--- title: Serverless Fullstack description: Deploy full-stack SSR frameworks using Lambda, S3, and CloudFront --- import { Icon } from 'astro-icon/components'; import { Aside } from '@astrojs/starlight/components'; import ServerlessArchitecture from '@/components/docs/patterns/ServerlessArchitecture.astro'; import ServerlessPipeline from '@/components/docs/patterns/ServerlessPipeline.astro'; Deploy full-stack server-side rendered applications on AWS using [Lambda](https://aws.amazon.com/lambda/) for SSR, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) as the global CDN. Scales to zero — pay only for actual requests. ## Supported Frameworks - [Nuxt](https://nuxt.com/) - [Astro](https://astro.build/) - [TanStack Start](https://tanstack.com/start/latest) - [SvelteKit](https://kit.svelte.dev/) - [Solid Start](https://start.solidjs.com/) - [AnalogJS](https://analogjs.org/) - Any Nitro or Vite-based SSR framework via the generic `Serverless` construct ## AWS Resources | Resource | Purpose | | --- | --- | | [Lambda Function](https://aws.amazon.com/lambda/) | Runs SSR and API routes | | [S3 Bucket](https://aws.amazon.com/s3/) | Hosts static assets (JS, CSS, images) | | [CloudFront Distribution](https://aws.amazon.com/cloudfront/) | Global CDN with origin routing | | [Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) | Secures S3 — no public bucket access | | [ACM Certificate](https://aws.amazon.com/certificate-manager/) | SSL/TLS for custom domain (optional) | | [Route53](https://aws.amazon.com/route53/) | DNS management (optional) | ## Architecture CloudFront routes requests at the edge using this logic: | Request pattern | Routed to | Notes | | --- | --- | --- | | `*.*` (any file extension) | S3 | JS, CSS, images, fonts — long-term cached | | `/api/*` (or custom `paths`) | Lambda | API routes, mutations | | Everything else `/*` | Lambda | SSR page rendering | ## Quick Start ### Installation ```bash bun add -D @thunder-so/thunder ``` ### Configuration Use the framework-specific construct for best defaults. Each construct knows the expected build output paths for its framework and configures Lambda, S3, and CloudFront accordingly. ```ts title="stack/prod.ts" import { Cdk, Nuxt, type NuxtProps } from '@thunder-so/thunder'; const config: NuxtProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1', }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serverProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, keepWarm: true, }, }; new Nuxt( new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config ); ``` ### Deploy ```bash bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` CDK outputs the CloudFront URL: ``` Outputs: myapp-web-prod-stack.CloudFrontUrl = https://d1234abcd.cloudfront.net ``` ## Custom Domain A certificate in `us-east-1` is required for CloudFront: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... domain: 'app.example.com', hostedZoneId: 'Z1D633PJN98FT9', certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123', }; ``` ## Advanced Configuration ### Server Runtime Fine-tune Lambda performance with memory, timeout, concurrency, and warm-up settings. `streaming` enables response streaming for frameworks that support it (Nuxt/Nitro). ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, tracing: true, // enable AWS X-Ray keepWarm: true, // ping every 5 min to prevent cold starts streaming: true, // enable response streaming (Nitro) reservedConcurrency: 10, // hard cap on simultaneous executions provisionedConcurrency: 2, // pre-warmed instances, eliminates cold starts }, }; ``` ### Custom API Paths By default, CloudFront routes `/api/*` to Lambda and everything else either to S3 (static files) or Lambda (SSR). Use `paths` to customize which URL patterns are treated as API routes: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { paths: ['/api/*', '/trpc/*', '/auth/*'], }, }; ``` ### Environment Variables and Secrets ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123' }, ], }, }; ``` ### CloudFront Cache Behavior Control what gets included in the CloudFront cache key for SSR responses: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... allowHeaders: ['Accept-Language'], allowCookies: ['session-*'], allowQueryParams: ['lang', 'theme'], // denyQueryParams: ['utm_source', 'fbclid'], // mutually exclusive with allowQueryParams }; ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. For apps with large dependencies, switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html) (up to 10 GB). ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Generic Serverless Construct For any Vite/Nitro-based framework not explicitly supported, use the generic `Serverless` construct and specify the server output paths manually: ```ts title="stack/prod.ts" import { Cdk, Serverless, type ServerlessProps } from '@thunder-so/thunder'; const config: ServerlessProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serverProps: { codeDir: '.output/server', handler: 'index.handler', runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, }, clientProps: { outputDir: '.output/public', }, }; new Serverless(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ## CI/CD Pipeline ### AWS CodePipeline Integration ```ts title="stack/prod.ts" const config: NuxtProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, buildProps: { runtime: 'nodejs', runtime_version: '22', installcmd: 'bun install', buildcmd: 'bun run build', }, }; ``` ## Stack Outputs | Output | Description | | --- | --- | | `CloudFrontUrl` | CloudFront distribution URL | | `Route53Domain` | Custom domain URL (only if `domain` is configured) | ## Destroy ```bash npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default ```