Next.js

Deployment & Hosting

20 min Lesson 26 of 40

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

  1. Set up environment variables for development and production
  2. Create a Dockerfile for your Next.js application
  3. Deploy to Vercel using GitHub integration
  4. Configure custom domain and SSL certificate
  5. Implement ISR for a blog page with 1-hour revalidation
  6. Set up Sentry for error tracking
  7. Create a GitHub Actions workflow for automated deployment
  8. 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.