Next Deployment¶
Vercel is the one-click experience. Docker is the self-hosted control. output: "standalone" is the production build that doesn't need node_modules weighing 400MB
Next.js deployment isn't just "npm run build && npm start". Your rendering strategy , caching behavior , and environment variable injection depend entirely on where and how you deploy. Pick wrong and your ISR pages never revalidate or your Edge functions timeout on every request
Vercel deployment¶
The platform built by the same team that built Next.js. It just works - most of the time
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel # Preview deployment
vercel --prod # Production deployment
# Or connect your GitHub repo for automatic deployments
Vercel handles:
- Automatic HTTPS - SSL certificates managed for you
- Edge Network - 100+ edge locations globally
- ISR at the edge - incremental static regeneration runs on edge nodes
- Image optimization - built-in image CDN
- Environment variables - per-environment configuration in dashboard
Vercel pricing notes (2026):
- Hobby tier - free , 100GB bandwidth , 6000 build minutes/month , 1 concurrent build
- Pro tier - $20/user/month , 1TB bandwidth , 6000 build minutes , unlimited teams
- Enterprise - custom pricing , SLA , dedicated support
The big cost drivers: bandwidth (images), build minutes (frequent deployments), and Edge function invocations
Docker self-hosting¶
For full control over infrastructure
# Dockerfile
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --only=production
# Build the application
FROM base AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Use standalone output (minimal production build)
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
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
Build and run:
docker build -t my-next-app .
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e AUTH_SECRET="your-secret" \
my-next-app
standalone output¶
By default , next build creates a .next/ directory with everything including node_modules. That's huge
Enable output: "standalone" in next.config.ts to create a minimal production build:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'standalone'
}
export default nextConfig
After npm run build:
.next/
standalone/
server.js # Minimal production server
.next/ # Compiled output
node_modules/ # Only production dependencies
static/ # Static assets (separate for CDN)
The standalone directory can be copied and deployed without the original source code. Docker images built with standalone output are 80-90% smaller
# Extract standalone for deployment
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
CMD ["node", "server.js"]
environment variables per environment¶
Each deployment environment needs its own configuration
# .env.production
NEXT_PUBLIC_SITE_URL=https://myapp.com
DATABASE_URL=postgresql://prod:password@prod-host:5432/myapp
# .env.preview
NEXT_PUBLIC_SITE_URL=https://preview-myapp.vercel.app
DATABASE_URL=postgresql://staging:password@staging-host:5432/myapp
On Vercel , set environment variables in the project dashboard per environment:
Production: DATABASE_URL , AUTH_SECRET , NEXT_PUBLIC_SITE_URL
Preview: DATABASE_URL_PREVIEW , AUTH_SECRET , NEXT_PUBLIC_SITE_URL
Development: (uses .env.local on your machine)
For Docker , pass environment variables at runtime:
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e AUTH_SECRET="..." \
-e NEXT_PUBLIC_SITE_URL="https://myapp.com" \
my-next-app
CRITICAL: NEXT_PUBLIC_ variables are inlined at BUILD time. In Docker , this means they must be available during docker build , not docker run. For runtime-configurable public variables , use a different approach like a config API endpoint
other deployment platforms¶
Node.js server (bare metal / VPS):
npm run build
npm start
# or
node .next/standalone/server.js
AWS Elastic Beanstalk: Zip the project with a Procfile:
web: npm run build && npm start
Cloudflare Pages: Limited support. Next.js features like ISR and Middleware don't work on Cloudflare Pages
Netlify: Uses the @netlify/plugin-nextjs adapter. Most features work but Edge functions are limited compared to Vercel
Vercel vs Docker vs VPS¶
| Feature | Vercel | Docker | VPS |
|---|---|---|---|
| Setup time | Minutes | Hours | Days |
| Scaling | Automatic | Manual | Manual |
| Edge functions | Yes | No (requires CDN) | No |
| ISR at edge | Yes | No (single region) | No |
| Image optimization | Built-in | Manual CDN setup | Manual CDN setup |
| Cost at scale | High (bandwidth) | Predictable | Predictable |
| Control | Limited | Full | Full |
| Cold starts | Yes (serverless) | No | No |
Start on Vercel. Move to Docker when costs justify the infrastructure work. Most apps never need to leave Vercel
prerequisites¶
next_12_env_config.md - you should understand environment variables and build-time vs runtime values
next → next_14_performance.md