Vercel zero-config and preview deployments, self-hosting with standalone output, static exports for CDN-only deployments, Docker multi-stage builds, PWA setup, and GitHub Actions CI pipelines.
Who this is for: Practitioners ready to ship. You've built the application — now you need to understand where it can run, what the trade-offs are between deployment targets, and how to build a CI/CD pipeline that catches problems before they reach users. This module covers the full deployment matrix: Vercel, self-hosted Node.js, Docker, and static export.
Every Next.js application can be deployed in three fundamentally different ways, and the choice affects which features work:
Vercel — zero-configuration, all features supported, preview deployments per PR, built-in ISR/PPR/edge compute. The path of least resistance and the right choice for most applications.
Self-hosted Node.js (standalone output) — full Next.js feature support (ISR, streaming, Server Actions, Route Handlers), requires a persistent Node.js process or container orchestration, more operational overhead.
Static export — pure HTML/CSS/JS files served from any CDN. No server required, no ISR, no Route Handlers that use Node.js APIs, no dynamic routes without workarounds. The right choice when you genuinely need zero server dependency.
The decision is not about preference — it's about which features your application uses.
Vercel is the path with the least distance between code and production. Push to your main branch, it deploys. No configuration required for a Next.js application — Vercel detects it automatically and configures the build correctly.
Environment variables per environment:
# Three scopes in Vercel dashboard: Development, Preview, Production # Development: pulled by vercel env pull .env.local # Preview: used for PR preview deployments # Production: used for main branch deploys
vercel env add DATABASE_URL production vercel env add DATABASE_URL preview vercel env add DATABASE_URL development
The Vercel CLI lets you pull remote env vars into a local .env.local:
npm install -g vercel vercel link # link local project to Vercel project vercel env pull # writes .env.local from development env vars
Preview deployments are the feature that pays for itself in minutes. Every pull request gets its own live URL — https://my-app-git-feature-branch-team.vercel.app. Your team reviews real running code, not static screenshots. Preview deployments use the Preview environment variables, so they can point at a staging database.
Zero-downtime deploys are automatic on Vercel. The old version continues serving traffic until the new deployment is ready. Instant rollback to any previous deployment via the dashboard or CLI:
After npm run build, Next.js generates .next/standalone — a minimal self-contained directory with only the Node.js server and its actual runtime dependencies (no node_modules bloat from dev dependencies or unused packages). Typical size reduction: from 500MB to 40–80MB.
The standalone server runs with:
node .next/standalone/server.js # Serves on port 3000 by default # PORT=8080 node .next/standalone/server.js ← custom port
You need to copy two additional directories alongside the standalone output — Next.js doesn't include them automatically:
# Required: static files and public assets cp -r .next/static .next/standalone/.next/static cp -r public .next/standalone/public
The canonical Dockerfile for a standalone Next.js application:
# Stage 1: Install dependencies — full install, not --only=production. # The builder stage below needs devDependencies (TypeScript, Tailwind, etc.) # to run `next build`. Production pruning happens for free later: the # standalone output copied into the runner stage only includes the # dependencies Next.js actually traced as needed at runtime. FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci
# Stage 2: Build the application FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . .
# Build args for public env vars (NEXT_PUBLIC_*) ARG NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run build
# Stage 3: Production runner FROM node:20-alpine AS runner WORKDIR /app
Three stages keep the final image small — the builder stage has all dev tools, the runner has only what's needed at runtime.
Non-root user is a security requirement for most container security policies. The nextjs user owns the .next directory so ISR can write revalidated pages.
NEXT_PUBLIC_* vars at build time — these are baked into the JavaScript bundle during build, not read at runtime. Pass them as build args.
Server-side env vars at runtime — DATABASE_URL, AUTH_SECRET, and other server-only vars are injected at container run time via -e or an env file, not baked into the image.
npm run build generates an out/ directory of plain HTML files. Deploy it to any static host: S3 + CloudFront, Cloudflare Pages, Netlify, GitHub Pages.
Hard constraints with static export:
No ISR (no server to revalidate)
No Route Handlers that use Node.js APIs or dynamic request data
No Middleware
No cookies() or headers() in Server Components
Image optimization needs a custom loader (Cloudinary, imgix, or unoptimized: true)
All dynamic routes must have generateStaticParams
Static export is the right choice for marketing sites, documentation, and any application where the content is fully known at build time and a CDN is sufficient.
// Validate server env at module load — throws during build if vars are missing export const serverEnv = serverEnvSchema.parse(process.env); export const clientEnv = clientEnvSchema.parse({ NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL, });
Import serverEnv from any Server Component or Route Handler. The build fails immediately with a descriptive Zod error if a required variable is absent — much better than discovering the missing variable at runtime after deploy.
Return 200 when healthy, 503 when degraded. Load balancers stop routing to instances that return 503 — this is how you get automatic failover when a database connection fails.
The Practitioner phase is complete. You can build, authenticate, cache, test, configure, and deploy production Next.js applications. The Architect phase begins with A-1 — the RSC internals that explain why the framework behaves the way it does at the protocol level. Understanding the React Flight wire format is what separates engineers who debug Next.js confidently from engineers who guess.
Knowledge Check
Which deployment target is required if your application relies heavily on Incremental Static Regeneration (ISR) but you cannot use Vercel?
When building a Docker image for a standalone Next.js application, how should public environment variables (NEXT_PUBLIC_*) be handled?
What is a hard constraint of using the static export (output: 'export') in Next.js?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.