Deploy TanStack Start on AWS
Full-stack React framework with type-safe routing and server functions.
tanstack.com/startDeploy your TanStack Start applications to AWS using Thunder. Choose the pattern that fits your app’s needs.
Available Patterns
Prerequisites
Getting Started
Create Project
Scaffold a new TanStack Start project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development.
bunx create-tanstack-start my-appcd my-appnpx create-tanstack-start my-appcd my-apppnpm create @tanstack/start@latest my-appcd my-appInstall Thunder
Add Thunder as a development dependency. It provides the CDK constructs you’ll use to define your AWS infrastructure.
bun add @thunder-so/thunder --developmentnpm install @thunder-so/thunder --save-devpnpm add -D @thunder-so/thunderTanStack Start Static Site Deployment
Deploy a fully pre-rendered TanStack Start site to S3 with CloudFront as the CDN. Every page is generated at build time and served as static files — no server required.
Configure
Set the Nitro preset to static in your app config. This tells TanStack Start to pre-render all routes at build time and output them to .output/public/.
import { defineConfig } from '@tanstack/start/config';
export default defineConfig({ server: { preset: 'static', },});Stack
The Static construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record.
import { Cdk, Static, type StaticProps } from '@thunder-so/thunder';
const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: '.output/public',};
new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);Deploy
Build your app first to generate the static files, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution.
bun run buildnpx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultnpm run buildnpx cdk deploy --app "npx tsx stack/prod.ts" --profile defaultpnpm run buildpnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile defaultAfter deployment, CDK outputs a CloudFront URL where your static site is live.
TanStack Start Containerized Deployment with Fargate
Run your TanStack Start app as a Node.js server inside a Docker container on ECS Fargate. Traffic is routed through an Application Load Balancer. This pattern supports full SSR, API routes, and any server-side logic.
Configure for Node Server
TanStack Start uses Nitro as its server engine. Set the preset to node-server so the build output is a standard Node.js HTTP server that can run inside a container.
import { defineConfig } from '@tanstack/start/config';
export default defineConfig({ server: { preset: 'node-server', },});Stack
The Fargate construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer.
import { Cdk, Fargate, type FargateProps } from '@thunder-so/thunder';
const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', },};
new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);Dockerfile
Create a Dockerfile in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime.
FROM oven/bun:latest AS builderWORKDIR /appCOPY package.json bun.lockb ./RUN bun install --frozen-lockfileCOPY . .RUN bun run build
FROM oven/bun:latest AS runnerWORKDIR /appENV NODE_ENV=productionENV HOST=0.0.0.0ENV PORT=3000COPY --from=builder /app/.output ./EXPOSE 3000CMD ["bun", "run", "server/index.mjs"]Environment Variables and Secrets
Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in AWS Secrets Manager and reference them by ARN — Thunder fetches and injects them automatically.
const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], },};Deploy
CDK builds the Docker image, pushes it to ECR, and deploys it to Fargate. No manual Docker commands needed.
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultnpx cdk deploy --app "npx tsx stack/prod.ts" --profile defaultpnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile defaultAfter deployment, CDK outputs the Load Balancer DNS for your application.
TanStack Start Serverless Fullstack Deployment
Deploy TanStack Start with SSR using AWS Lambda for server-side rendering, S3 for static assets, and CloudFront to unify both behind a single domain. This pattern scales to zero and charges only for actual requests.
Configure TanStack Start for AWS Lambda
TanStack Start uses Nitro as its server engine. You must explicitly set the aws-lambda preset — the default node-server preset outputs an HTTP server, not a Lambda handler, and will not work on Lambda.
import { defineConfig } from '@tanstack/start/config';import { nitro } from 'nitro/vite';
export default defineConfig({ vite: { plugins: [ nitro({ preset: 'aws-lambda' }), ], },});The build will produce .output/server/ (Lambda handler) and .output/public/ (static assets for S3).
Stack (Zip mode)
The TanStackStart construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps.
import { Cdk, TanStackStart, type TanStackStartProps } from '@thunder-so/thunder';
const config: TanStackStartProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.',};
new TanStackStart(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);Container Mode
Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to ECR, and deploys it as a container Lambda, which supports up to 10 GB.
Stack (Container mode)
Add dockerFile to serverProps to enable container mode.
const config: TanStackStartProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, },};Dockerfile
FROM public.ecr.aws/lambda/nodejs:22
# Copy all lambda filesCOPY . ./
CMD ["index.handler"]Environment Variables and Secrets
Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in AWS Secrets Manager and reference them by ARN — Thunder fetches and injects them automatically.
const config: TanStackStartProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], },};Deploy
Build your app first to generate the Lambda handler and static assets, then deploy with CDK.
bun run buildnpx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultnpm run buildnpx cdk deploy --app "npx tsx stack/prod.ts" --profile defaultpnpm run buildpnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile defaultAfter deployment, CDK outputs a CloudFront URL that serves both your SSR responses and static assets.