Skip to content
Open
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
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