Skip to content

Commit dc6ee69

Browse files
authored
feat: deployment adapter for AWS Lambda (#318)
This PR adds a new built-in **AWS Lambda** deployment adapter to `@lazarv/react-server`, enabling users to deploy their React Server applications to AWS with a single configuration change. The adapter leverages AWS SAM (Serverless Application Model) to provision a Lambda function with streaming support and an optional CloudFront CDN distribution. ## Motivation AWS Lambda is one of the most widely used serverless platforms. Until now, the `aws` option in `create-react-server` was marked as "coming soon." This PR delivers the full implementation, completing the AWS deployment story alongside the existing Vercel, Netlify, Cloudflare, Azure, Bun, and Deno adapters. ## What's Included ### AWS Lambda Adapter (`packages/react-server/adapters/aws/index.mjs`) The core adapter that hooks into the react-server build pipeline: - **Edge build mode** — bundles the server into a single file optimized for Lambda deployment. - **Static file bundling** — copies all static assets (pre-rendered HTML, client components, public files, build assets) into the Lambda deployment package and generates a build-time manifest (`static-manifest.json`) for O(1) lookups at runtime. - **SAM template generation** — produces a `template.json` with: - An `AWS::Serverless::Function` resource configured with Function URL in `RESPONSE_STREAM` invoke mode for streaming SSR. - A custom `AWS::CloudFront::CachePolicy` that defers to origin `Cache-Control` headers (DefaultTTL/MinTTL = 0, MaxTTL = 1 year for immutable assets). - A `AWS::CloudFront::Distribution` with a single Lambda origin — all requests (static and dynamic) flow through one origin, avoiding CloudFront Function size limits, Origin Group failover issues, and hardcoded path patterns. - **Configurable options** — `name`, `stackName`, `runtime`, `memorySize`, `timeout`, `architecture`, `authType`, `environment`, `functionProperties`, `cloudfront`, `resources`, `outputs`, `template`, and `deployArgs`. - **Deploy command** — exposes a `sam deploy --guided` command for first-time guided deployment. ### Lambda Function Handler (`packages/react-server/adapters/aws/functions/handler.mjs`) The runtime handler that executes inside Lambda: - **Static file serving** — intercepts GET requests and serves files from the bundled static manifest with appropriate `Cache-Control` headers: - Build assets (`/assets/`, `/client/`): `public, max-age=31536000, immutable` - HTML / x-component: `must-revalidate` - Public files: `public, max-age=600` - **SSR via react-server** — all non-static requests (and all non-GET requests) are forwarded to the react-server edge handler. - **Streaming support** — uses `awslambda.streamifyResponse()` when available (Lambda Function URLs with `RESPONSE_STREAM` mode), falling back to buffered API Gateway v2 responses. - **Request translation** — converts Lambda events (API Gateway v2 / Function URL format) into standard Web `Request` objects, including cookie handling and base64-encoded body support. - **Cookie forwarding** — merges `set-cookie` headers from react-server's HTTP context into the response. - **Error handling** — returns 404 for unmatched routes and 500 with error details for server errors. ### Documentation - **English docs** (`docs/src/pages/en/(pages)/deploy/aws.mdx`) — comprehensive guide covering prerequisites (AWS CLI, SAM CLI), installation, full configuration reference, build & deploy workflow, architecture overview, and advanced customization. - **Japanese docs** (`docs/src/pages/ja/(pages)/deploy/aws.mdx`) — translated version of the English documentation. - **Adapters index pages** (EN & JA) — updated to list AWS Lambda as a supported adapter with a note about SAM CLI requirements. ### Scaffolding (`packages/create-react-server/steps/deploy.mjs`) - Removed the `disabled: "(coming soon)"` flag from the AWS option in the deployment step. - Added `"AWS Lambda"` to the adapter name map and `.aws`, `template.json`, `samconfig.toml` to the gitignore entries for the adapter. ## Architecture ``` Viewer → CloudFront (edge cache) → Lambda Function URL (RESPONSE_STREAM) │ ├── Static file? → Serve from disk (with Cache-Control) │ → CloudFront caches at edge │ └── Dynamic request → react-server SSR → Stream response back ``` Key design decisions: - **Single Lambda origin** — all traffic goes through one Lambda function, avoiding CloudFront Function size limits (10KB), Origin Group failover limitations (GET/HEAD only), and hardcoded path patterns that could conflict with user routes. - **Static files bundled in Lambda** — eliminates the need for a separate S3 bucket origin. CloudFront caches static responses at the edge after the first request, so subsequent requests never hit Lambda. - **Streaming by default** — uses `RESPONSE_STREAM` invoke mode for real-time SSR streaming, with automatic fallback to buffered responses for API Gateway v2.
1 parent 1130ca7 commit dc6ee69

10 files changed

Lines changed: 1257 additions & 3 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ deno.lock
1717
.azure-swa
1818
.bun
1919
.deno
20+
.aws
21+
template.json
22+
samconfig.toml
2023
*.pem
2124
*.sqlite
2225
*.log

docs/src/components/AdapterGrid.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const adapters = [
1414
href: "/deploy/cloudflare",
1515
description: "Workers & Pages",
1616
},
17+
{
18+
name: "AWS Lambda",
19+
href: "/deploy/aws",
20+
description: "Serverless functions",
21+
},
1722
{
1823
name: "Bun",
1924
href: "/deploy/bun",

docs/src/pages/en/(pages)/deploy/adapters.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ You can use adapters to configure your app for different deployment environments
1717
- [x] Vercel
1818
- [x] Netlify
1919
- [x] Cloudflare Workers/Pages
20+
- [x] AWS Lambda
2021
- [x] Bun
2122
- [x] Deno
2223
- [x] Azure Functions
@@ -26,10 +27,12 @@ You can use adapters to configure your app for different deployment environments
2627
## Configuration
2728
</Link>
2829

29-
Add `adapter` entry to your `react-server.config.mjs` file. You can specify the name of a built-in adapter (`vercel`, `netlify`, `cloudflare`, `bun`, `deno`, `azure`, or `azure-swa`) as a string, or use an external adapter package.
30+
Add `adapter` entry to your `react-server.config.mjs` file. You can specify the name of a built-in adapter (`vercel`, `netlify`, `cloudflare`, `aws`, `bun`, `deno`, `azure`, or `azure-swa`) as a string, or use an external adapter package.
3031

3132
> **Note:** When running a production build with **Bun** or **Deno**, the corresponding adapter is automatically detected and used without any configuration. You can override this with an explicit `adapter` setting in your config or via `--adapter <name>` on the CLI. Use `--no-adapter` to disable auto-detection.
3233
34+
> **Note:** The `aws` adapter requires the [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) and configured AWS credentials. See the [AWS Lambda](/deploy/aws) page for setup instructions.
35+
3336
```mjs
3437
export default {
3538
adapter: 'vercel',
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
---
2+
title: AWS Lambda
3+
category: Deploy
4+
order: 4
5+
---
6+
7+
import Link from "../../../../components/Link.jsx";
8+
9+
# AWS Lambda
10+
11+
To deploy to AWS, use the built-in `aws` adapter. This adapter deploys your application as an AWS Lambda function with a CloudFront CDN distribution, using AWS SAM (Serverless Application Model) for infrastructure management.
12+
13+
<Link name="prerequisites">
14+
## Prerequisites
15+
</Link>
16+
17+
You need the following tools installed:
18+
19+
- **AWS CLI** — for managing AWS credentials
20+
- **AWS SAM CLI** — for building and deploying the SAM template
21+
22+
```sh
23+
brew install awscli aws-sam-cli
24+
```
25+
26+
Then configure your AWS credentials:
27+
28+
```sh
29+
aws configure
30+
```
31+
32+
You'll need your AWS Access Key ID and Secret Access Key. You can create these in the [AWS IAM Console](https://console.aws.amazon.com/iam/) under **Users → Security credentials → Access keys**.
33+
34+
<Link name="installation">
35+
## Installation
36+
</Link>
37+
38+
No additional packages are needed — the adapter is built into `@lazarv/react-server`.
39+
40+
Add the adapter to your `react-server.config.mjs` file:
41+
42+
```mjs
43+
export default {
44+
adapter: "aws",
45+
};
46+
```
47+
48+
<Link name="configuration">
49+
## Configuration
50+
</Link>
51+
52+
You can customize the adapter by passing options:
53+
54+
```mjs
55+
export default {
56+
adapter: [
57+
"aws",
58+
{
59+
name: "my-app", // Application name (used in resource names)
60+
stackName: "my-app-stack", // CloudFormation stack name
61+
runtime: "nodejs20.x", // Lambda runtime (default: "nodejs20.x")
62+
memorySize: 1024, // Lambda memory in MB (default: 1024)
63+
timeout: 30, // Lambda timeout in seconds (default: 30)
64+
architecture: "arm64", // Lambda architecture (default: "arm64")
65+
authType: "NONE", // Function URL auth type (default: "NONE")
66+
environment: { // Additional environment variables
67+
MY_API_KEY: "value",
68+
},
69+
},
70+
],
71+
};
72+
```
73+
74+
### Configuration Options
75+
76+
- `name`: Application name used for AWS resource names. Falls back to `package.json` name (without scope) or `"react-server-app"`.
77+
- `stackName`: CloudFormation stack name used during deployment. Defaults to `name`.
78+
- `runtime`: Lambda runtime identifier (default: `"nodejs20.x"`).
79+
- `memorySize`: Lambda function memory in MB (default: `1024`).
80+
- `timeout`: Lambda function timeout in seconds (default: `30`).
81+
- `architecture`: Lambda CPU architecture, `"arm64"` or `"x86_64"` (default: `"arm64"`). Arm64 (Graviton2) offers better price-performance.
82+
- `authType`: Function URL authentication type (default: `"NONE"`). Set to `"AWS_IAM"` to require IAM auth.
83+
- `environment`: Additional environment variables added to the Lambda function. `NODE_ENV` is always set to `"production"`.
84+
- `functionProperties`: Additional SAM `AWS::Serverless::Function` properties merged into the function resource.
85+
- `cloudfront`: Set to `false` to skip CloudFront distribution creation. Use `cloudfront.distributionConfig` to extend the generated CloudFront `DistributionConfig`.
86+
- `resources`: Additional CloudFormation resources merged into the template.
87+
- `outputs`: Additional CloudFormation outputs merged into the template.
88+
- `template`: Override or extend the SAM template. Pass an object to merge, or a function `(template) => template` for full control.
89+
- `deployArgs`: Additional arguments passed to `sam deploy`.
90+
91+
<Link name="deploy">
92+
## Deploy
93+
</Link>
94+
95+
Build and deploy your application:
96+
97+
```sh
98+
pnpm react-server build [root] --adapter aws
99+
sam deploy --guided --stack-name my-app --capabilities CAPABILITY_IAM
100+
```
101+
102+
Or use the `--deploy` flag to build and deploy in one step:
103+
104+
```sh
105+
pnpm react-server build [root] --adapter aws --deploy
106+
```
107+
108+
The first deployment uses `--guided` mode which prompts for configuration and saves it to `samconfig.toml`. Subsequent deployments use the saved configuration automatically.
109+
110+
<Link name="architecture">
111+
## Architecture
112+
</Link>
113+
114+
The adapter creates the following AWS resources:
115+
116+
- **Lambda Function** with a Function URL (streaming enabled via `RESPONSE_STREAM`)
117+
- **CloudFront Distribution** as a CDN in front of the Lambda function
118+
- **CloudFront Cache Policy** that respects origin `Cache-Control` headers
119+
120+
### How it works
121+
122+
All requests flow through a single Lambda origin:
123+
124+
```
125+
Viewer → CloudFront → Lambda
126+
├─ Static file? → Serve from disk with Cache-Control
127+
└─ Dynamic? → SSR via react-server
128+
```
129+
130+
The Lambda handler includes all static files in its deployment package and serves them directly from the filesystem. Each response includes appropriate `Cache-Control` headers:
131+
132+
- **Build assets** (`/assets/*`, `/client/*`): `public, max-age=31536000, immutable` — cached at the edge for 1 year (filenames are content-hashed)
133+
- **Pre-rendered HTML / x-component**: `must-revalidate` — CloudFront always revalidates with Lambda
134+
- **Public files**: `public, max-age=600` — cached for 10 minutes
135+
136+
After the first request, CloudFront serves cached static files directly from the edge without invoking Lambda. The custom cache policy (`DefaultTTL: 0`, `MinTTL: 0`, `MaxTTL: 31536000`) defers entirely to the origin's `Cache-Control` headers.
137+
138+
<Link name="cloudfront-configuration">
139+
## CloudFront Configuration
140+
</Link>
141+
142+
### Custom domain
143+
144+
To use a custom domain with your CloudFront distribution, extend the distribution config:
145+
146+
```mjs
147+
export default {
148+
adapter: [
149+
"aws",
150+
{
151+
cloudfront: {
152+
distributionConfig: {
153+
Aliases: ["www.example.com"],
154+
ViewerCertificate: {
155+
AcmCertificateArn: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
156+
SslSupportMethod: "sni-only",
157+
MinimumProtocolVersion: "TLSv1.2_2021",
158+
},
159+
},
160+
},
161+
},
162+
],
163+
};
164+
```
165+
166+
> **Note:** ACM certificates for CloudFront must be in the `us-east-1` region.
167+
168+
### Without CloudFront
169+
170+
To deploy without CloudFront (Lambda Function URL only):
171+
172+
```mjs
173+
export default {
174+
adapter: [
175+
"aws",
176+
{
177+
cloudfront: false,
178+
},
179+
],
180+
};
181+
```
182+
183+
<Link name="sam-template">
184+
## SAM Template
185+
</Link>
186+
187+
The adapter generates a `template.json` file in your project root. This is a standard AWS SAM template that can be customized via `adapterOptions.template`:
188+
189+
```mjs
190+
export default {
191+
adapter: [
192+
"aws",
193+
{
194+
template: (template) => {
195+
// Add a DynamoDB table
196+
template.Resources.MyTable = {
197+
Type: "AWS::DynamoDB::Table",
198+
Properties: {
199+
TableName: "my-table",
200+
AttributeDefinitions: [
201+
{ AttributeName: "id", AttributeType: "S" },
202+
],
203+
KeySchema: [
204+
{ AttributeName: "id", KeyType: "HASH" },
205+
],
206+
BillingMode: "PAY_PER_REQUEST",
207+
},
208+
};
209+
return template;
210+
},
211+
},
212+
],
213+
};
214+
```
215+
216+
<Link name="troubleshooting">
217+
## Troubleshooting
218+
</Link>
219+
220+
### `zsh: command not found: sam`
221+
222+
Install the AWS SAM CLI:
223+
224+
```sh
225+
brew install aws-sam-cli
226+
```
227+
228+
### `zsh: command not found: aws`
229+
230+
Install the AWS CLI:
231+
232+
```sh
233+
brew install awscli
234+
```
235+
236+
### `Error: Unable to locate credentials`
237+
238+
Configure your AWS credentials:
239+
240+
```sh
241+
aws configure
242+
```
243+
244+
You'll need your Access Key ID, Secret Access Key, default region, and output format. Visit the [AWS IAM Console](https://console.aws.amazon.com/iam/) to create access keys.
245+
246+
If you're using AWS SSO:
247+
248+
```sh
249+
aws sso login --profile your-profile
250+
```
251+
252+
### `Failed to create/update the stack ... ROLLBACK_COMPLETE`
253+
254+
A previous deployment failed and CloudFormation is stuck. Delete the failed stack and try again:
255+
256+
```sh
257+
sam delete --stack-name your-stack-name
258+
sam deploy --guided --stack-name your-stack-name --capabilities CAPABILITY_IAM
259+
```
260+
261+
### Lambda cold starts
262+
263+
Cold starts are expected with Lambda functions. To minimize their impact:
264+
265+
- Use `arm64` architecture (default) — Graviton2 processors have faster cold starts
266+
- Increase `memorySize` — Lambda allocates CPU proportional to memory
267+
- Consider using [Lambda Provisioned Concurrency](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html) for production workloads
268+
269+
You can add provisioned concurrency via the template override:
270+
271+
```mjs
272+
export default {
273+
adapter: [
274+
"aws",
275+
{
276+
template: (template) => {
277+
const fnKey = Object.keys(template.Resources).find(
278+
(k) => template.Resources[k].Type === "AWS::Serverless::Function"
279+
);
280+
if (fnKey) {
281+
template.Resources[fnKey].Properties.ProvisionedConcurrencyConfig = {
282+
ProvisionedConcurrentExecutions: 1,
283+
};
284+
}
285+
return template;
286+
},
287+
},
288+
],
289+
};
290+
```
291+
292+
### CloudFront cache invalidation
293+
294+
After redeploying, CloudFront may still serve stale cached content. Create an invalidation:
295+
296+
```sh
297+
aws cloudfront create-invalidation \
298+
--distribution-id YOUR_DISTRIBUTION_ID \
299+
--paths "/*"
300+
```
301+
302+
Build assets with content-hashed filenames (`/assets/*`, `/client/*`) don't need invalidation — new deployments generate new filenames automatically.
303+
304+
> **Note:** For additional AWS-specific features like VPC configuration, IAM policies, or Lambda layers, use the `functionProperties`, `resources`, and `template` options. Refer to the [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/) for the full template specification.

docs/src/pages/ja/(pages)/deploy/adapters.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Link from "../../../../components/Link.jsx";
1717
- [x] Vercel
1818
- [x] Netlify
1919
- [x] Cloudflare Workers/Pages
20+
- [x] AWS Lambda
2021
- [x] Bun
2122
- [x] Deno
2223
- [x] Azure Functions
@@ -26,10 +27,12 @@ import Link from "../../../../components/Link.jsx";
2627
## 設定
2728
</Link>
2829

29-
`react-server.config.mjs`ファイルに `adapter` エントリを追加します。ビルトインアダプタの名前(`vercel``netlify``cloudflare``bun``deno``azure`、または `azure-swa`)を文字列で指定するか、外部アダプタパッケージを使用できます。
30+
`react-server.config.mjs`ファイルに `adapter` エントリを追加します。ビルトインアダプタの名前(`vercel``netlify``cloudflare``aws``bun``deno``azure`、または `azure-swa`)を文字列で指定するか、外部アダプタパッケージを使用できます。
3031

3132
> **Note:** **Bun** または **Deno** でプロダクションビルドを実行すると、対応するアダプタが自動的に検出・使用されます。設定は不要です。明示的な `adapter` 設定またはCLIの `--adapter <name>` で上書きできます。自動検出を無効にするには `--no-adapter` を使用してください。
3233
34+
> **Note:** `aws` アダプタには [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) と設定済みのAWS認証情報が必要です。セットアップ手順は [AWS Lambda](/deploy/aws) ページを参照してください。
35+
3336
```mjs
3437
export default {
3538
adapter: 'vercel',

0 commit comments

Comments
 (0)