Skip to content

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