---
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
```