Deployment & Hosting
Deploying Next.js applications involves understanding build processes, hosting platforms, environment configuration, and optimization techniques. This lesson covers everything from deploying to Vercel to self-hosting with Docker.
Understanding Next.js Build Process
Before deploying, you need to understand what happens during the build process:
# Development mode - hot reload, unoptimized
npm run dev
# Production build - optimized static files
npm run build
# Preview production build locally
npm run start
The build process generates several outputs:
.next/
├── cache/ # Build cache for faster rebuilds
├── server/ # Server-side code and API routes
├── static/ # Static assets with content hashes
└── BUILD_ID # Unique build identifier
Deploying to Vercel
Vercel is the recommended platform for Next.js, offering seamless integration and optimal performance:
Why Vercel for Next.js? Vercel is created by the Next.js team, provides automatic optimization, edge caching, serverless functions, and preview deployments out of the box.
Method 1: GitHub Integration (Recommended)
# 1. Push your code to GitHub
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/username/repo.git
git push -u origin main
# 2. Connect to Vercel
# - Visit vercel.com and sign in with GitHub
# - Import your repository
# - Configure project settings
# - Deploy automatically
Method 2: Vercel CLI
# Install Vercel CLI
npm i -g vercel
# Login to Vercel
vercel login
# Deploy to preview
vercel
# Deploy to production
vercel --prod
Environment Variables in Vercel
Configure environment variables through the dashboard or CLI:
// In your Vercel project settings:
// Environment Variables → Add new
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=your-secret-here
// Via CLI
vercel env add DATABASE_URL
vercel env add NEXT_PUBLIC_API_URL
Environment Variable Prefixes: Only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Never prefix secrets with NEXT_PUBLIC_!
Self-Hosting with Node.js
You can host Next.js on any Node.js server (VPS, AWS EC2, DigitalOcean, etc.):
Basic Setup
# Build the application
npm run build
# Start production server
NODE_ENV=production npm start
# With custom port
PORT=3000 npm start
Using PM2 for Process Management
# Install PM2 globally
npm install -g pm2
# Start application with PM2
pm2 start npm --name "nextjs-app" -- start
# Configure with ecosystem file
// ecosystem.config.js
module.exports = {
apps: [{
name: 'nextjs-app',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production',
PORT: 3000
},
instances: 'max',
exec_mode: 'cluster',
max_memory_restart: '1G'
}]
};
# Start with config
pm2 start ecosystem.config.js
# Other PM2 commands
pm2 list # List all processes
pm2 logs nextjs-app # View logs
pm2 restart nextjs-app
pm2 stop nextjs-app
pm2 delete nextjs-app
Docker Deployment
Containerize your Next.js application for consistent deployments:
Multi-Stage Dockerfile (Optimized)
# Dockerfile
FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
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
CMD ["node", "server.js"]
Standalone Output Configuration
Enable standalone output for smaller Docker images:
// next.config.js
module.exports = {
output: 'standalone',
};
Docker Compose for Development
# docker-compose.yml
version: '3.8'
services:
nextjs:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# Build and run
docker-compose up --build
Build Optimization
Optimize your build for production:
Bundle Analysis
# Install bundle analyzer
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// Your existing config
});
# Analyze bundle
ANALYZE=true npm run build
Code Splitting Configuration
// Dynamic imports for code splitting
import dynamic from 'next/dynamic';
// Lazy load heavy components
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Loading...</p>,
ssr: false // Disable SSR for this component
});
// Preload critical components
const CriticalComponent = dynamic(() => import('./Critical'), {
loading: () => <p>Loading...</p>
});
Image Optimization Settings
// next.config.js
module.exports = {
images: {
domains: ['example.com', 'cdn.example.com'],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
dangerouslyAllowSVG: false,
},
};
Static Site Generation (SSG) Deployment
For fully static sites, export to static HTML:
// next.config.js
module.exports = {
output: 'export',
images: {
unoptimized: true // Required for static export
}
};
# Build static site
npm run build
# Output directory
out/
├── index.html
├── about.html
├── _next/
└── ...
# Deploy to static hosting
# - Netlify
# - GitHub Pages
# - AWS S3 + CloudFront
# - Cloudflare Pages
CDN and Caching Strategy
Implement effective caching for optimal performance:
Cache Headers Configuration
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, s-maxage=10, stale-while-revalidate=59'
}
]
},
{
source: '/:all*(svg|jpg|png|webp|avif)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable'
}
]
}
];
}
};
ISR (Incremental Static Regeneration) Strategy
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
export const revalidate = 3600; // Revalidate every hour
export default async function Post({ params }) {
const post = await getPost(params.slug);
return <article>{post.content}</article>;
}
Environment Configuration Best Practices
Manage environments effectively across development, staging, and production:
Environment Files Structure
.env.local # Local development (gitignored)
.env.development # Development defaults
.env.production # Production defaults
.env.test # Testing environment
# .env.local example
DATABASE_URL=postgresql://localhost:5432/dev
NEXT_PUBLIC_API_URL=http://localhost:3000/api
SECRET_KEY=dev-secret-key
# .env.production example
DATABASE_URL=${{ secrets.DATABASE_URL }}
NEXT_PUBLIC_API_URL=https://api.production.com
SECRET_KEY=${{ secrets.SECRET_KEY }}
Runtime Configuration
// lib/config.ts
export const config = {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
isDev: process.env.NODE_ENV === 'development',
isProd: process.env.NODE_ENV === 'production',
};
// Use in components
import { config } from '@/lib/config';
export default function Component() {
const apiUrl = config.apiUrl;
// ...
}
Monitoring and Error Tracking
Implement monitoring for production applications:
Sentry Integration
# Install Sentry
npm install @sentry/nextjs
# Initialize Sentry
npx @sentry/wizard -i nextjs
// sentry.client.config.js
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
});
// sentry.server.config.js
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});
CI/CD Pipeline
Automate deployment with GitHub Actions:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
Exercise: Deploy Your Application
- Set up environment variables for development and production
- Create a Dockerfile for your Next.js application
- Deploy to Vercel using GitHub integration
- Configure custom domain and SSL certificate
- Implement ISR for a blog page with 1-hour revalidation
- Set up Sentry for error tracking
- Create a GitHub Actions workflow for automated deployment
- Analyze your bundle size and optimize heavy imports
Production Checklist: Before deploying, ensure all environment variables are set, error tracking is configured, security headers are enabled, images are optimized, and you have a rollback strategy.