Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions examples/hello-world-aws-lambda/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.aws-lambda/
.react-server/
cdk.out/
51 changes: 51 additions & 0 deletions examples/hello-world-aws-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Hello World (AWS Lambda)

This example builds a minimal React Server app and bundles a Lambda handler that runs behind API Gateway v2.

## Try it locally

Build the example:

```sh
pnpm build
```

Run the Lambda handler locally with debug logging and the safety auto-end enabled:

```sh
# print request/response lifecycle logs
# auto-end finishes the response shortly after first write (API Gateway v2 isn't streaming)
DEBUG_AWS_LAMBDA_ADAPTER=1 \
pnpm dlx lambda-handler-tester@latest --handler .aws-lambda/output/functions/index.func/index.mjs
```

You should see a 200 response with a small HTML body and logs like:

```
[react-server][response.write] { type: 'object', length: 66, encoding: undefined }
[react-server][response.writeHead] { statusCode: 200, ... }
[react-server][auto-end] forcing res.end() after write
```

## Environment flags

- `DEBUG_AWS_LAMBDA_ADAPTER`
- Enables verbose logs. Accepts: `1`, `true`, `yes`.

## Deploying

The build outputs a self-contained function folder at:

```
.aws-lambda/output/functions/index.func/
```

You can deploy this Lambda behind an API Gateway v2 HTTP API using your preferred tooling (CDK/Terraform/Serverless/etc.).

If you're using the adapter's default deploy hint, run:

```sh
npx cdk deploy
```

Note: API Gateway v2 won’t stream the payload; the auto-end guard prevents hung responses.
25 changes: 25 additions & 0 deletions examples/hello-world-aws-lambda/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@lazarv/react-server-example-hello-world",
"private": true,
"description": "@lazarv/react-server Hello World example application",
"scripts": {
"dev": "react-server",
"dev:app": "react-server ./App.jsx",
"build": "react-server build",
"build:app": "react-server build ./App.jsx",
"start": "react-server start",
"test:lambda": "pnpm dlx lambda-handler-tester@latest",
"clean": "rm -rf .react-server .aws-lambda"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@lazarv/react-server": "workspace:^",
"@lazarv/react-server-adapter-aws-lambda": "workspace:^"
},
"devDependencies": {
"aws-cdk-lib": "^2.221.1",
"constructs": "^10.4.2"
}
}
11 changes: 11 additions & 0 deletions examples/hello-world-aws-lambda/react-server.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"root": "src/pages",
"public": "src/public",
"adapter": [
"@lazarv/react-server-adapter-aws-lambda",
{
"streaming": true,
"routingMode": "pathBehaviors"
}
]
}
29 changes: 29 additions & 0 deletions examples/hello-world-aws-lambda/src/components/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";
import { useState } from "react";

export default function Counter() {
const [count, setCount] = useState(0);

return (
<div className="flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold">Counter</h1>
<p className="mt-4 text-center">
The current count is <span className="font-bold">{count}</span>.
</p>
<div className="mt-4 space-x-4">
<button
className="px-4 py-2 text-white bg-blue-500 rounded"
onClick={() => setCount((prevCount) => prevCount + 1)}
>
Increment
</button>
<button
className="px-4 py-2 text-white bg-red-500 rounded"
onClick={() => setCount((prevCount) => prevCount - 1)}
>
Decrement
</button>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// app/StreamingList.jsx (Server Component)
import { Suspense } from "react";

import { getChunk } from "../data/getBigList";

const TOTAL_ITEMS = 10_000;
const CHUNK_SIZE = 100;

export default function StreamingList({
totalItems = TOTAL_ITEMS,
chunkSize = CHUNK_SIZE,
}) {
// Calculate number of chunks but don't fetch data yet
const numChunks = Math.ceil(totalItems / chunkSize);
const chunkIndexes = Array.from({ length: numChunks }, (_, i) => i);

return (
<div style={{ maxHeight: "50vh", overflow: "auto" }}>
<h1>Streaming {totalItems.toLocaleString()} items</h1>
<ul>
{chunkIndexes.map((chunkIndex) => (
<Suspense
key={chunkIndex}
fallback={
<ChunkPlaceholder index={chunkIndex} chunkSize={chunkSize} />
}
>
{/* Each Chunk fetches its own data independently - enabling true streaming */}
<Chunk
index={chunkIndex}
chunkSize={chunkSize}
totalItems={totalItems}
/>
</Suspense>
))}
</ul>
</div>
);
}

function ChunkPlaceholder({ index, chunkSize }) {
return (
<>
<li>
Loading items {index * chunkSize + 1} - {(index + 1) * chunkSize} …
</li>
</>
);
}

// Async server component for a chunk - fetches its own data
async function Chunk({ index, chunkSize, totalItems }) {
// Each chunk independently fetches data with progressive delay
const items = await getChunk(index, totalItems, chunkSize);

return (
<>
{items.map((item, i) => (
<li key={index * chunkSize + i}>{item}</li>
))}
</>
);
}
22 changes: 22 additions & 0 deletions examples/hello-world-aws-lambda/src/data/getBigList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Get a specific chunk with simulated delay (for true streaming)
export async function getChunk(
chunkIndex,
totalItems = 10_000,
chunkSize = 100
) {
// Simulate progressive data fetching - each chunk takes incrementally longer
// This creates a waterfall effect where chunks appear one after another
const delay = chunkIndex * 100; // 0ms, 100ms, 200ms, 300ms, etc.

if (delay > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
}

const startIndex = chunkIndex * chunkSize;
const endIndex = Math.min(startIndex + chunkSize, totalItems);

return Array.from(
{ length: endIndex - startIndex },
(_, i) => `Item #${startIndex + i + 1}`
);
}
72 changes: 72 additions & 0 deletions examples/hello-world-aws-lambda/src/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
h1 {
font-family: "Courier New", Courier, monospace;
}
/* Tailwind CSS classes */
.bg-blue-500 {
background-color: #3b82f6;
}
.mt-4 {
margin-top: 1rem;
}

.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.text-white {
color: #ffffff;
}

.p-4 {
padding: 1rem;
}

.rounded {
border-radius: 0.25rem;
}
.w-full {
width: 100%;
}

.max-w-full {
max-width: 100%;
}

.h-auto {
height: auto;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}

.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}

.bg-red-500 {
background-color: #ef4444;
}

.flex {
display: flex;
}

.flex-col {
flex-direction: column;
}

.items-center {
align-items: center;
}

.justify-center {
justify-content: center;
}

.h-screen {
height: 100vh;
}
/* Add more Tailwind CSS classes as needed */
16 changes: 16 additions & 0 deletions examples/hello-world-aws-lambda/src/pages/(404).[...slug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { status } from "@lazarv/react-server";
import { Link } from "@lazarv/react-server/navigation";

export default function NotFound() {
status(404);

return (
<div className="fixed inset-0 flex flex-col gap-4 items-center justify-center">
<h1 className="text-4xl font-bold">Not Found</h1>
<p className="text-lg">The page you are looking for does not exist.</p>
<Link to="/" root noCache>
Go back to the home page
</Link>
</div>
);
}
25 changes: 25 additions & 0 deletions examples/hello-world-aws-lambda/src/pages/(root).layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import "../global.css";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AWS Deploy App</title>
</head>
<body>
<div className="p-4">
<h1 className="text-4xl font-bold mb-4">
<a href="/">AWS Deploy App</a>
</h1>
{children}
</div>
</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default true;
31 changes: 31 additions & 0 deletions examples/hello-world-aws-lambda/src/pages/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Link } from "@lazarv/react-server/navigation";

export default async function AboutPage() {
return (
<div>
<title>About 01</title>
<h1 className="text-4xl font-bold tracking-tight">About (static)</h1>
<img
src="/static/images/image-placeholder.svg"
alt="placeholder"
className="w-full max-w-full h-auto"
/>
<p>This is placeholder for a Textblock.</p>
<Link to="/" className="mt-4 inline-block underline">
Return home
</Link>
|{" "}
<Link to="/s/page" className="mt-4 inline-block underline">
Page (static/no preload)
</Link>
|{" "}
<Link to="/s/hello" className="mt-4 inline-block underline">
Hello (static)
</Link>
|{" "}
<Link to="/s/page/hello" className="mt-4 inline-block underline">
Hello (dynamic)
</Link>
</div>
);
}
17 changes: 17 additions & 0 deletions examples/hello-world-aws-lambda/src/pages/client/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from "@lazarv/react-server/navigation";

export default async function ClientPage() {
return (
<div>
<title>ClientPage</title>
<h1 className="text-4xl font-bold tracking-tight">
ClientPage (dynamic)
</h1>

<p>Overlaps with static content.</p>
<Link to="/" className="mt-4 inline-block underline">
Return home
</Link>
</div>
);
}
Loading
Loading