Skip to content

Latest commit

 

History

History
212 lines (157 loc) · 4.93 KB

File metadata and controls

212 lines (157 loc) · 4.93 KB

Deploy TanStack Start to AWS Lambda + S3 + CloudFront

Deploy your TanStack Start app with server-side rendering to AWS using Thunder's TanStackStart construct. This creates a hybrid architecture: Lambda handles SSR and API routes, S3 hosts static assets, and CloudFront unifies both.

1. Create a New TanStack Start Project

bunx create-tanstack-start my-app
cd my-app

Reference: TanStack Start Quick Start

2. Configure Nitro for AWS Lambda

TanStack Start uses Nitro for server-side rendering. You must explicitly set the aws-lambda preset - the default is node-server which won't work on Lambda.

Edit app.config.ts (or create it if it doesn't exist):

import { defineConfig } from "@tanstack/start/config";
import { nitro } from "nitro/vite";

export default defineConfig({
  vite: {
    plugins: [
      nitro({
        preset: "aws-lambda", // Required for Lambda deployment
      }),
    ],
  },
});

Alternative: If using @tanstack/nitro-v2-vite-plugin:

import { nitroV2Plugin } from "@tanstack/nitro-v2-vite-plugin";

export default defineConfig({
  vite: {
    plugins: [
      nitroV2Plugin({
        preset: "aws-lambda",
      }),
    ],
  },
});

Reference: Nitro AWS Lambda Preset

3. Build Your App

bun run build

This generates:

  • .output/server/ - Lambda handler
  • .output/public/ - Static assets for S3

4. Install Thunder

bun add @thunder-so/thunder --development

5. Create Stack File

Create stack/prod.ts:

import {
  Cdk,
  TanStackStart,
  type TanStackStartProps,
} from "@thunder-so/thunder";

const config: TanStackStartProps = {
  env: {
    account: "123456789012", // Your AWS 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,
);

6. Deploy

npx cdk deploy --app "npx tsx stack/prod.ts" --profile default

CDK outputs the CloudFront and API Gateway URLs:

Outputs:
myapp-web-prod-stack.CloudFrontUrl = https://d1234abcd.cloudfront.net
myapp-web-prod-stack.ApiGatewayUrl = https://abc123.execute-api.us-east-1.amazonaws.com

Custom Domain (Optional)

  1. Create a Route53 Hosted Zone
  2. Request a global ACM certificate in us-east-1 (for CloudFront)
  3. Request a regional ACM certificate in your function's region (for API Gateway)

Update your stack:

const config: TanStackStartProps = {
  // ...
  domain: "app.example.com",
  hostedZoneId: "Z1234567890ABC",
  globalCertificateArn:
    "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
  regionalCertificateArn:
    "arn:aws:acm:us-east-1:123456789012:certificate/def-456",
};

Environment Variables

const config: TanStackStartProps = {
  // ...
  serverProps: {
    variables: [
      { NODE_ENV: "production" },
      { API_BASE_URL: "https://api.example.com" },
    ],
  },
};

Secrets from AWS Secrets Manager

aws secretsmanager create-secret \
  --name "/myapp/DATABASE_URL" \
  --secret-string "postgres://user:pass@host/db"
serverProps: {
  secrets: [
    { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123' },
  ],
},

Performance Tuning

serverProps: {
  memorySize: 1792,  // More memory = more CPU
  timeout: 10,
  keepWarm: true,    // Ping every 5 min to prevent cold starts
  tracing: true,     // Enable AWS X-Ray
},

Container Mode (Optional)

For larger apps, use Docker:

Create Dockerfile:

FROM public.ecr.aws/lambda/nodejs:22

COPY .output/server/ ./

CMD ["index.handler"]

Update your stack:

serverProps: {
  dockerFile: 'Dockerfile',
  memorySize: 2048,
},

Troubleshooting

Error: Runtime.HandlerNotFound: index.handler is undefined

You forgot to set preset: 'aws-lambda' in your Nitro config. The default node-server preset outputs an HTTP server, not a Lambda handler.

Static assets not loading

Check that your build output is in .output/public/. Thunder automatically deploys this to S3 and routes *.* requests through CloudFront.

Related