
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Most teams struggle when they attempt to deploy Next.js to production because they treat it like a static site or a generic Node app without accounting for its hybrid rendering model. The framework’s default development server is unsuitable for live traffic, and skipping the standalone output mode leads to bloated containers and slow deployments. This guide provides the exact configuration patterns I use to ship resilient, audit-ready Next.js applications on self-managed infrastructure.
standalone output in your config, build a multi-stage Docker container that copies only required artifacts, and front it with Nginx as a reverse proxy handling TLS and compression. Automate this workflow via CI/CD to ensure reproducible, secure releases every time you ship.How do you configure Next.js standalone output for production?
The single most critical step when you deploy Next.js to production on your own servers is enabling the standalone output mode. Without this flag, Next.js expects the entire node_modules directory to be present at runtime, resulting in Docker images exceeding 1GB and deployment times stretching into minutes. The standalone mode uses Node.js tracing to identify exactly which files are needed and copies them into .next/standalone.
In your next.config.js, set the output explicitly:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// Enable compression for smaller payloads
compress: true,
// Security headers applied at the framework level
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
],
},
]
},
}
module.exports = nextConfig After running next build, inspect the .next/standalone directory. You will notice it contains a server.js file and a trimmed node_modules folder. However, a common mistake is forgetting to copy the .next/static and public directories manually — the standalone tracer does not include these because they are served separately by your reverse proxy or CDN. If you skip this step, your CSS, JavaScript chunks, and images will return 404 errors in production.
What is the optimal Dockerfile for Next.js standalone builds?
A multi-stage Dockerfile is non-negotiable when you deploy Next.js to production. The goal is to separate build-time dependencies (TypeScript, ESLint, testing libraries) from the runtime image. I recommend a three-stage approach: base, builder, and runner. This pattern keeps the final image under 150MB and reduces your attack surface significantly.
# Stage 1: Base - Install dependencies
FROM node:22-alpine AS base
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm fetch --prod
# Stage 2: Builder - Build the application
FROM base AS builder
WORKDIR /app
COPY . .
RUN corepack enable pnpm && pnpm install --frozen-lockfile
RUN pnpm build
# Stage 3: Runner - Minimal production image
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME="0.0.0.0"
ENV PORT=3000
# Create non-root user for security compliance
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
# Copy standalone output + static assets
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"] Several details here matter for operational stability. Setting HOSTNAME="0.0.0.0" is mandatory; without it, the Node server binds only to localhost inside the container and refuses external connections. Using pnpm over npm or yarn typically yields faster CI builds and more deterministic installs due to its content-addressable storage. Always run as a non-root user — this is a basic requirement for SOC 2 compliance and prevents container breakout exploits from gaining host-level privileges.
If your team needs deeper context on securing container environments before deploying, review our guide on Ubuntu security hardening for host-level defenses that complement container isolation.
How should Nginx reverse proxy be configured for Next.js?
Never expose the Next.js Node server directly to the internet. Nginx handles TLS termination, gzip/brotli compression, static file serving, rate limiting, and request buffering far more efficiently than Node. When you deploy Next.js to production behind Nginx, you also gain centralized access logging and the ability to perform zero-downtime reloads during deployments.
This configuration assumes your Next.js container runs on port 3000 locally:
upstream nextjs_backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Serve static assets directly, bypassing Node
location /_next/static/ {
alias /var/www/nextjs/.next/static/;
expires 365d;
add_header Cache-Control "public, immutable";
}
location /public/ {
alias /var/www/nextjs/public/;
expires 30d;
}
# Proxy all other requests to Next.js
location / {
proxy_pass http://nextjs_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache_bypass $http_upgrade;
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
} The keepalive 64 directive maintains persistent connections between Nginx and Node, eliminating TCP handshake overhead on every request. Disabling proxy_buffering is essential for Server-Sent Events and streaming responses — if you use AI chat interfaces or real-time data feeds in your Next.js app, buffered proxies will break them. For teams managing database-backed Next.js apps, understanding PostgreSQL administration essentials ensures your data layer doesn't become the bottleneck after optimizing the web tier.
How do you automate Next.js deployment with CI/CD pipelines?
Manual deployments violate every principle of reliable operations. When you deploy Next.js to production repeatedly, you need an automated pipeline that builds, tests, scans, and ships without human intervention. Below is a GitHub Actions workflow I have refined across multiple client projects:
- Checkout and setup: Use
actions/setup-nodewith exact Node version matching your Dockerfile base image. - Dependency install: Run
pnpm install --frozen-lockfileto guarantee reproducible builds. - Quality gates: Execute linting, type checking, and unit tests before building. Fail fast.
- Docker build: Build the multi-stage image and tag with both Git SHA and
latest. - Security scan: Run Trivy or Snyk against the built image. Block deployment on critical CVEs.
- Push to registry: Upload to ECR, GHCR, or your private registry only after passing all gates.
- Deploy: SSH into the target server or trigger Kubernetes rollout with the new image tag.
name: Deploy Next.js
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint && pnpm test
- run: pnpm build
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Security scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: '1'
- name: Push and deploy
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login -u deploy --password-stdin
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
ssh deploy@prod-server "docker pull registry.example.com/myapp:${{ github.sha }} && docker compose up -d" This pipeline enforces that no untested, unscanned code reaches production. The security scan step is particularly important for teams pursuing ISO 27001 or SOC 2 compliance — auditors will ask for evidence that you validate container images before deployment. Integrating observability early is equally vital; consider reading our comparison of metrics, logs, and traces to instrument your Next.js app before it hits production traffic.
Vercel vs self-hosted: which Next.js deployment strategy fits?
Choosing where to deploy Next.js to production depends on your team’s operational capacity, budget, and compliance requirements. Vercel offers zero-config deployments but at significant cost for high-traffic applications. Self-hosting demands more upfront work but provides predictable pricing and full control.
| Criteria | Vercel / Managed Platform | Self-Hosted (Docker + Nginx) |
|---|---|---|
| Setup Time | Minutes | Hours to days |
| Monthly Cost (1M requests) | $20–$100+ | $5–$20 VPS |
| Data Residency Control | Limited regions | Full control |
| Compliance (SOC 2, ISO) | Vendor-dependent | Self-managed evidence |
| Edge Functions | Native support | Requires Cloudflare/CDN |
| Custom Server Middleware | Restricted | Unrestricted |
| Long-running Requests | Timeout limits (10–60s) | No artificial limits |
For Nepal-based companies handling local user data or fintech applications, self-hosting on a regional VPS or colocated server often satisfies data residency requirements that managed platforms cannot meet. Conversely, early-stage startups validating product-market fit benefit from Vercel’s instant preview deployments and automatic edge caching. The right choice evolves as your traffic, team size, and regulatory obligations change.
Deploy Next.js to Production: Final Checklist and Next Steps
Successfully shipping Next.js requires treating it as a first-class server application, not an afterthought. Verify these items before going live:
- Standalone output enabled and tested locally
- Multi-stage Dockerfile producing images under 200MB
- Nginx configured with proper upstream keepalive and static asset caching
- TLS certificates automated via Let’s Encrypt or equivalent
- CI/CD pipeline with security scanning blocking critical vulnerabilities
- Health check endpoint (
/api/health) wired to load balancer probes - Structured logging with correlation IDs for request tracing
- Environment variables injected at runtime, never baked into images
If your team needs hands-on assistance architecting a production-grade Next.js deployment, implementing observability, or preparing for compliance audits, reach out to discuss your infrastructure needs. I help engineering teams build systems that are secure, observable, and ready for growth from day one.