Deploying Next.js on AWS: a hands-on guide for engineers
When you're ready to move a Next.js application from your laptop to production, AWS offers multiple deployment paths—each with different trade-offs in complexity, cost, and operational overhead. We've run production Next.js workloads across Amplify, serverless frameworks, and container platforms, and the right choice depends on your team's scale, existing AWS footprint, and tolerance for infrastructure management.
This guide walks through the primary deployment strategies we use in the field, with practical examples so you can evaluate which fits your situation.
Amplify Hosting: managed and Git-native
Amplify Hosting is AWS's managed platform for frontend applications. If your team wants the fastest path from code push to live without managing infrastructure, this is often the best starting point.
Setup is straightforward: connect your GitHub, GitLab, or CodeCommit repository, and Amplify watches for commits to your main branch. On each push, it runs your build step, deploys to CloudFront, and provisions SSL automatically. For a standard Next.js app with SSG pages and API routes, this works without configuration changes.
Here's what the Amplify configuration file looks like:
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
env:
variables:
NODE_VERSION: 18.17.0Amplify works well for teams that prioritize deployment simplicity. Environment variables are managed through the console, and you get built-in branch deployments for pull requests—useful for review workflows. Logging and error tracking integrate with CloudWatch and X-Ray without extra setup.
The main limitation: Amplify runs your build on their infrastructure, which means the cold start for builds can take 3–5 minutes on first deployment. If you're deploying frequently or have a complex build pipeline, this adds up. Additionally, Amplify doesn't support all Next.js features equally; streaming SSR, for instance, has limited support, and image optimization requires careful tuning.
Cost is typically $0.15 per build minute plus data transfer, so small to mid-size projects often stay under $50/month. Large organizations may find the managed overhead worth it; smaller teams sometimes outgrow the constraints.
OpenNext and SST: serverless deployments
If you want fine-grained control and don't want to pay for idle compute, serverless is compelling. OpenNext is a community-driven adapter that transforms a Next.js app into a Lambda-compatible bundle. Paired with SST (Serverless Stack), you get Infrastructure-as-Code and fast local development.
OpenNext handles the tricky parts: it separates your SSR routes into Lambda handlers, pre-renders static pages, and bundles assets for CloudFront. Here's how to set up a minimal SST config:
import { SSTConfig } from "sst";
import { NextjsSite } from "sst/constructs";
export default {
config(_input) {
return {
name: "my-next-app",
region: "us-east-1",
};
},
stacks(app) {
app.stack(function Site({ stack }) {
const site = new NextjsSite(stack, "site", {
path: ".",
});
stack.addOutputs({
SiteUrl: site.url,
});
});
},
} satisfies SSTConfig;When you deploy with sst deploy, SST creates the Lambda function for your API routes, sets up S3 for static assets, and configures CloudFront to route requests appropriately. The build process is local and fast because you control the Node version and dependencies.
Real-world example: we deployed an e-commerce storefront this way and saved ~$200/month compared to running a dedicated container. During off-peak hours, Lambda pay-per-invocation pricing meant we were paying almost nothing. ISR (Incremental Static Regeneration) cached pages in CloudFront, so cache hits never touched Lambda.
The trade-off is operational complexity. You need to understand Lambda's 15-minute timeout limit, ephemeral /tmp storage, and cold starts (typically 1–3 seconds on a fresh invocation). Long-running background jobs don't fit the serverless model; you'll need SQS or EventBridge for async work.
Also, OpenNext is community-maintained, not an official AWS project. It tracks Next.js releases closely, but sometimes you'll hit edge cases before they're ironed out. Test thoroughly in staging.
ECS or EC2 with standalone output
For teams already running containerized workloads on AWS, deploying Next.js to ECS is often the pragmatic choice. You get full control, predictable performance, and integration with your existing monitoring and logging.
Next.js's standalone output mode produces a minimal, self-contained application server that doesn't require the full source directory. This is key for Docker images:
// next.config.js
module.exports = {
output: 'standalone',
// other config
}Here's a typical Dockerfile:
FROM node:18-alpine AS base
WORKDIR /app
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM base AS runner
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/public ./public
COPY --from=build /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]Deploy this to ECS with Fargate (serverless containers) or EC2 instances. We typically use Fargate for new projects because it removes the burden of patching and managing the underlying compute. Set up an Application Load Balancer, enable auto-scaling based on CPU/memory, and you have a robust production setup.
Configuration via CloudFormation or CDK is straightforward. Here's a minimal CDK example:
const fargateService = new ecs.FargateService(this, 'NextService', {
cluster,
taskDefinition,
desiredCount: 2,
});
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc,
internetFacing: true,
});
alb.addTarget(fargateService);This approach scales predictably. We run e-commerce checkout flows on Fargate and can handle traffic spikes by increasing the desired count from 2 to 10 instances in under a minute. Cost is typically $50–200/month depending on instance size and traffic, plus data transfer.
The operational cost is higher: you need to monitor task health, manage logs in CloudWatch, and handle deployments (though ECS integrates with CodePipeline for CI/CD). But for teams with engineering capacity and existing ECS patterns, it's a solid choice.
Image optimization and ISR cache strategy
Regardless of which deployment path you choose, Next.js image optimization can become a bottleneck. By default, Next.js serves optimized images through its internal image server, which works fine at small scale but adds latency and compute cost at production scale.
The best practice is to pre-optimize images at build time and serve them through CloudFront. Here's how we typically configure it:
// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './lib/imageLoader.js',
},
}Then define a custom loader that points to CloudFront:
// lib/imageLoader.js
export default function imageLoader({ src, width, quality }) {
return `https://cdn.example.com/images/${src}?w=${width}&q=${quality || 75}`;
}For ISR, set a revalidation interval on your pages:
export async function getStaticProps() {
const data = await fetchProductData();
return {
props: { data },
revalidate: 3600, // revalidate every hour
};
}CloudFront will cache this page aggressively. On revalidation, only a single request goes to your origin (Lambda, container, or Amplify), and all other requests are served from the edge. This pattern keeps origins cool and response times fast.
If you're using Amplify, configure cache behavior in the console. For ECS or serverless, add cache headers to your Next.js responses and set CloudFront behaviors explicitly.
Common mistakes
- Skipping the build step locally. Always run
npm run buildand test the output (npm run start) before deploying. Deployment-only bugs are the hardest to debug. - Not setting NODE_ENV=production. This breaks critical optimizations in Next.js and React. Verify it in your deployment logs.
- Ignoring cold starts in serverless. If your application needs sub-second response times consistently, serverless may not be the right fit. Load-test in your staging environment.
- Misconfiguring image optimization. If you're not pre-optimizing or caching images, every image request hits your origin and adds latency. Set up CloudFront or a dedicated CDN.
- Leaving build artifacts in the Docker image. Use multi-stage builds to keep image size under 200 MB. Fat images slow down deployments and increase cold start times.
Key takeaways
- Amplify Hosting is the fastest path to production for teams that prioritize simplicity over cost optimization; it handles Git-based deployments and SSL automatically.
- Serverless deployments with OpenNext and SST are ideal for variable traffic and cost-conscious teams, but require understanding Lambda's constraints (timeouts, cold starts, ephemeral storage).
- ECS with Fargate is the right choice for teams with existing container infrastructure or consistent, predictable traffic patterns.
- Image optimization is critical: use CloudFront caching or a dedicated CDN, and configure ISR revalidation to reduce origin load.
- Always test your build output locally before deploying; deployment-only bugs waste time and complicate troubleshooting.
- Monitor your deployment choice post-launch: track build times, cold start latency, and cost trends to validate your initial decision.






Comments
Loading comments…