@ballatech/react-router7-preset-aws
v1.0.1
Published
Minimal AWS Lambda preset for React Router v7. Inspired by the Architect adapter, but without pulling in Architect or its unrelated dependencies that can clash when using AWS CDK with CloudFront, S3, and Lambda to deploy a website.
Downloads
60
Readme
@ballatech/react-router7-preset-aws
Minimal AWS Lambda preset for React Router v7. Inspired by the Architect adapter, but without pulling in Architect or its unrelated dependencies that can clash when using AWS CDK with CloudFront, S3, and Lambda to deploy a website.
Why?
- Architect adapter inspiration: This preset mirrors the core request/response bridging needed to run React Router v7 on AWS Lambda (headers, cookies, binary body handling) and borrows the binary MIME list approach from Architect.
- No unrelated deps: The Architect ecosystem can pull in extra dependencies. When pairing with AWS CDK + CloudFront + S3 + Lambda, those can cause version or bundling conflicts. This preset stays tiny and focused.
Features
- React Router v7 on AWS Lambda (API Gateway v2)
- Zero runtime dependencies beyond React Router node utilities
- Binary response support (images, fonts, archives, etc.)
- Set-Cookie passthrough compatible with API Gateway
- Typed (TypeScript) API with a small surface area
Installation
pnpm add @ballatech/react-router7-preset-aws react-router @react-router/node
# or
npm i @ballatech/react-router7-preset-aws react-router @react-router/node
# or
yarn add @ballatech/react-router7-preset-aws react-router @react-router/nodePeer requirements:
- react-router: ^7
- @react-router/node: ^7
Usage
Create a Lambda handler that wires your React Router server build to API Gateway v2 events.
// server.ts
import { createRequestHandler } from '@ballatech/react-router7-preset-aws'
import type { ServerBuild } from 'react-router'
// Generated by `react-router build`
import * as build from './build/server'
export const handler = createRequestHandler({
build: build as unknown as ServerBuild,
// Optional: expose platform-specific values to loaders/actions
// getLoadContext: async (event) => ({
// viewerCountry: event.headers['cloudfront-viewer-country'],
// }),
})Notes:
- The handler expects API Gateway v2 events (HTTP API). It respects
x-forwarded-hostandhostheaders to construct the URL, which works behind CloudFront → API Gateway → Lambda. - Binary responses are automatically base64-encoded for content types in the common binary list.
- Multiple
Set-Cookieheaders are moved to the API Gatewaycookiesarray for proper delivery.
Build
Bundle the handler for Lambda after running the React Router build.
# Build the React Router app
react-router build
# Bundle the Lambda entry using esbuild (example)
node build.cjsExample build.cjs used by the examples in this repo:
const esbuild = require('esbuild')
esbuild
.build({
entryPoints: ['server.ts'],
bundle: true,
platform: 'node',
target: 'node22',
external: ['node:stream'],
outfile: 'build/lambda/index.cjs',
sourcemap: true,
format: 'cjs',
metafile: true,
legalComments: 'none',
})
.catch((error) => {
console.error('Build failed:', error)
throw error
})AWS CDK (example sketch)
This mirrors the working example in examples/my-react-router-app-infra. It hooks the Lambda to an HTTP API and fronts it with CloudFront. Static assets are served from S3 with optimized caching.
import * as path from 'node:path'
import { Stack, Duration } from 'aws-cdk-lib'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2'
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'
export class WebStack extends Stack {
constructor(scope: any, id: string) {
super(scope, id)
const bucket = new s3.Bucket(this, 'ClientBucket', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
})
const webFn = new lambdaNodejs.NodejsFunction(this, 'WebFn', {
runtime: lambda.Runtime.NODEJS_22_X,
memorySize: 1536,
timeout: Duration.minutes(5),
entry: path.resolve(__dirname, '../build/lambda/index.cjs'),
bundling: {
minify: true,
target: 'node22',
mainFields: ['module', 'main'],
externalModules: ['aws-sdk', '@aws-sdk/*'],
},
})
const api = new apigwv2.HttpApi(this, 'WebApi', {
defaultIntegration: new integrations.HttpLambdaIntegration('WebIntegration', webFn),
})
const distribution = new cloudfront.Distribution(this, 'Distribution', {
defaultBehavior: {
origin: new origins.HttpOrigin(
`${api.httpApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`,
),
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
additionalBehaviors: {
'assets/*': {
origin: new origins.S3Origin(bucket),
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
},
})
new s3deploy.BucketDeployment(this, 'DeployClient', {
destinationBucket: bucket,
sources: [s3deploy.Source.asset(path.resolve(__dirname, '../build/client'))],
distribution,
})
}
}CloudFront + S3 notes:
- Static assets: upload your app's static assets to S3 and front them with CloudFront.
- Dynamic requests: add a behavior in CloudFront that forwards SSR/app routes to the API Gateway origin. Ensure headers/cookies (
Host,X-Forwarded-Host, etc.) are forwarded as needed by your setup.
API
type GetLoadContextFunction = (event: APIGatewayProxyEventV2) => AppLoadContext | Promise<AppLoadContext>
createRequestHandler({
build: ServerBuild,
getLoadContext?: GetLoadContextFunction,
mode?: string, // defaults to process.env.NODE_ENV
}): APIGatewayProxyHandlerV2License
MIT © Ballatech
