Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/nextjs-streaming-response/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.vscode
.next
.git
.gitignore

node_modules

# aws sam
.aws-sam
samconfig.toml
3 changes: 3 additions & 0 deletions examples/nextjs-streaming-response/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
32 changes: 32 additions & 0 deletions examples/nextjs-streaming-response/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.vscode

# aws sam
./.aws-sam/
samconfig.toml

# dependencies
node_modules

# next.js
.next
out

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
23 changes: 23 additions & 0 deletions examples/nextjs-streaming-response/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM public.ecr.aws/lambda/nodejs:16 as builder
WORKDIR /app

COPY . .
RUN npm update && npm run build

FROM public.ecr.aws/lambda/nodejs:16 as runner
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.7.0 /lambda-adapter /opt/extensions/lambda-adapter

ENV PORT=3000 NODE_ENV=production

ENV AWS_LWA_ENABLE_COMPRESSION=false
ENV AWS_LWA_INVOKE_MODE=response_stream

WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/next.config.js ./next.config.js
RUN ln -s /tmp/cache ./.next/cache

ENTRYPOINT ["npm", "run", "start", "--loglevel=verbose", "--cache=/tmp/npm"]
22 changes: 22 additions & 0 deletions examples/nextjs-streaming-response/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Nextjs streaming response example

This example show how to use Lambda Web Adapter to run a nextjs application with streaming response via a [AWS Lambda](https://aws.amazon.com/lambda) Function URL.

### Build and Deploy

Run the following commands to build and deploy the application to lambda.

```bash
sam build

sam deploy --guided
```
When the deployment completes, the Function URL will appear in the output list, which is the entrypoint for accessing

### Verify it works

When you open the Function URL in a browser:

- Primary product information will be loaded first at part of the initial response

- Secondary, more personalized details (that might be slower) like recommended products and customer reviews are progressively streamed in.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Product } from "@/app/api/products/product";
import { ProductCard } from "@/ui/product-card";

export async function RecommendedProducts({
path,
data
}: {
path: string,
data: Promise<Response>
}) {

const products = (await data.then((res) => res.json())) as Product[]

return (
<div className="space-y-6">
<div>
<div className="text-lg font-medium text-white">
Recommended Products for You
</div>
<div className="text-sm text-gray-400">
Based on you preferences and shopping habits
</div>
</div>
<div className="grid grid-cols-4 gap-6">
{products.map((product) => (
<div key={product.id} className="col-span-4 lg:col-span-1">
<ProductCard product={product} href={`${path}/${product.id}`} />
</div>
))}
</div>
</div>
)

}


const shimmer = `relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent`;

function ProductSkeleton() {
return (
<div className="col-span-4 space-y-4 lg:col-span-1">
<div className={`relative h-[167px] rounded-xl bg-gray-900 ${shimmer}`} />

<div className="h-4 w-full rounded-lg bg-gray-900" />
<div className="h-6 w-1/3 rounded-lg bg-gray-900" />
<div className="h-4 w-full rounded-lg bg-gray-900" />
<div className="h-4 w-4/6 rounded-lg bg-gray-900" />
</div>
);
}

export function RecommendedProductsSkeleton() {
return (
<div className="space-y-6 pb-[5px]">
<div className="space-y-2">
<div className={`h-6 w-1/3 rounded-lg bg-gray-900 ${shimmer}`} />
<div className={`h-4 w-1/2 rounded-lg bg-gray-900 ${shimmer}`} />
</div>

<div className="grid grid-cols-4 gap-6">
<ProductSkeleton />
<ProductSkeleton />
<ProductSkeleton />
<ProductSkeleton />
</div>
</div>
);
}
43 changes: 43 additions & 0 deletions examples/nextjs-streaming-response/app/_components/reviews.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Review } from '@/app/api/reviews/review';
import { ProductReviewCard } from '@/ui/product-review-card';

export async function Reviews({ data }: { data: Promise<Response> }) {
const reviews = (await data.then((res) => res.json())) as Review[];

return (
<div className="space-y-6">
<div className="text-lg font-medium text-white">Customer Reviews</div>
<div className="space-y-8">
{reviews.map((review) => {
return <ProductReviewCard key={review.id} review={review} />;
})}
</div>
</div>
);
}

const shimmer = `relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent`;

function Skeleton() {
return (
<div className="space-y-4">
<div className="h-6 w-2/6 rounded-lg bg-gray-900" />
<div className="h-4 w-1/6 rounded-lg bg-gray-900" />
<div className="h-4 w-full rounded-lg bg-gray-900" />
<div className="h-4 w-4/6 rounded-lg bg-gray-900" />
</div>
);
}

export function ReviewsSkeleton() {
return (
<div className="space-y-6">
<div className={`h-7 w-2/5 rounded-lg bg-gray-900 ${shimmer}`} />

<div className="space-y-8">
<Skeleton />
<Skeleton />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Product } from "@/app/api/products/product"
import { ProductRating } from "@/ui/product-rating"
import Image from "next/image"

export const SingleProduct = async ({ data }: { data: Promise<Response> }) => {
const product = (await data.then((res) => res.json())) as Product

return (
<div className="grid grid-cols-4 gap-6">
<div className="col-span-full lg:col-span-1">
<div className="space-y-2">
<Image
src={`/${product.image}`}
className="hidden rounded-lg grayscale lg:block"
alt={product.name}
height={400}
width={400}
/>
<div className="flex gap-x-2">
<div className="w-1/3">
<Image
src={`/${product.image}`}
className="rounded-lg grayscale"
alt={product.name}
height={180}
width={180}
/>
</div>
<div className="w-1/3">
<Image
src={`/${product.image}`}
className="rounded-lg grayscale"
alt={product.name}
height={180}
width={180}
/>
</div>
<div className="w-1/3">
<Image
src={`/${product.image}`}
className="rounded-lg grayscale"
alt={product.name}
height={180}
width={180}
/>
</div>
</div>
</div>
</div>

<div className="col-span-full space-y-4 lg:col-span-2">
<div className="truncate text-xl font-medium text-white lg:text-2xl">
{product.name}
</div>

<ProductRating rating={product.rating} />

<div className="space-y-4 text-sm text-gray-200">
<p>{product.description}</p>
<p>{product.description}</p>
</div>

</div>
</div>
)
}
37 changes: 37 additions & 0 deletions examples/nextjs-streaming-response/app/api/products/product.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export type Product = {
id: string;
stock: number;
rating: number;
name: string;
description: string;
price: Price;
isBestSeller: boolean;
leadTime: number;
image?: string;
imageBlur?: string;
discount?: Discount;
usedPrice?: UsedPrice;
};

type Price = {
amount: number;
currency: Currency;
scale: number;
};

type Currency = {
code: string;
base: number;
exponent: number;
};

type Discount = {
percent: number;
expires?: number;
};

type UsedPrice = {
amount: number;
currency: Currency;
scale: number;
};
Loading