Deploy Next.js on AWS

The React framework for the web. Built by Vercel.

nextjs.org

Deploy your Next.js applications to AWS using Thunder. Choose the pattern that fits your app’s needs.

Available Patterns

Prerequisites

Getting Started

Create Project

Scaffold a new Next.js project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development.

Terminal window
bunx create-next-app@latest my-nextjs-app
cd my-nextjs-app

Install Thunder

Add Thunder as a development dependency. It provides the CDK constructs you’ll use to define your AWS infrastructure.

Terminal window
bun add @thunder-so/thunder --development

Next.js Static Export Deployment

Deploy Next.js as a fully static site to S3 with CloudFront as the CDN. In static export mode, Next.js pre-renders all pages at build time and outputs plain HTML, CSS, and JavaScript — no server required.

Configure

Set output: 'export' in your Next.js config to enable static export mode. Setting distDir to dist keeps the output directory consistent with other frameworks.

next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
distDir: 'dist',
};
export default nextConfig;

Stack

The Static construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record.

stack/prod.ts
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: 'dist',
};
new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);

Deploy

Build your Next.js app first to generate the static export, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution.

Terminal window
bun run build
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default

After deployment, CDK outputs a CloudFront URL where your static site is live.


Next.js Containerized Deployment with Fargate

Run your Next.js 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, image optimization, and all Next.js features.

Configure for Node Server

Set output: 'standalone' in your Next.js config. This tells Next.js to produce a minimal, self-contained server bundle in .next/standalone/ that includes only the files needed to run the app — ideal for Docker.

next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;

Stack

The Fargate construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer.

stack/prod.ts
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 uses Bun to install dependencies and build the app, then copies only the standalone output into a minimal Node.js runtime image.

Dockerfile
FROM public.ecr.aws/docker/library/node:22-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH"
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
FROM public.ecr.aws/docker/library/node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]

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.

stack/prod.ts
const config: FargateProps = {
// ...
serviceProps: {
// ...
variables: [
{ NODE_ENV: 'production' },
{ NEXT_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

CDK builds the Docker image, pushes it to ECR, and deploys it to Fargate. No manual Docker commands needed.

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

After deployment, CDK outputs the Load Balancer DNS for your application.