# Deployment & Production ## Vercel Deployment (Recommended) ### Quick Deploy ```bash # Install Vercel CLI npm i -g vercel # Deploy vercel # Production deployment vercel --prod ``` ### vercel.json Configuration ```json { "buildCommand": "next build", "devCommand": "next dev", "installCommand": "npm install", "framework": "nextjs", "regions": ["iad1"], "env": { "DATABASE_URL": "@database-url", "NEXT_PUBLIC_API_URL": "https://api.example.com" }, "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Access-Control-Allow-Origin", "value": "*" }, { "key": "Access-Control-Allow-Methods", "value": "GET,POST,PUT,DELETE" } ] } ], "redirects": [ { "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true } ], "rewrites": [ { "source": "/api/:path*", "destination": "https://api.example.com/:path*" } ] } ``` ### Environment Variables ```bash # .env.local (not committed) DATABASE_URL="postgresql://user:pass@localhost:5432/db" NEXTAUTH_SECRET="your-secret" # .env.production (committed, public vars only) NEXT_PUBLIC_API_URL="https://api.example.com" ``` ```tsx // Access in Server Components const dbUrl = process.env.DATABASE_URL // Access in Client Components (must be prefixed with NEXT_PUBLIC_) const apiUrl = process.env.NEXT_PUBLIC_API_URL ``` ## Self-Hosting ### Standalone Output ```js // next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone", } module.exports = nextConfig ``` ```bash # Build npm run build # The standalone folder contains everything needed # Copy these to your server: # - .next/standalone/ # - .next/static/ # - public/ # Run on server node .next/standalone/server.js ``` ### Node.js Server ```bash # Build npm run build # Start production server npm start # With PM2 for process management pm2 start npm --name "nextjs" -- start pm2 startup pm2 save ``` ## Docker Deployment ### Dockerfile (Multi-stage) ```dockerfile # Stage 1: Dependencies FROM node:20-alpine AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci # Stage 2: Builder FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build # Stage 3: Runner FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 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 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"] ``` ### docker-compose.yml ```yaml version: "3.8" services: nextjs: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - DATABASE_URL=postgresql://postgres:postgres@db:5432/myapp - NEXTAUTH_URL=http://localhost:3000 - NEXTAUTH_SECRET=your-secret depends_on: - db restart: unless-stopped db: image: postgres:16-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=myapp volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped volumes: postgres_data: ``` ```bash # Build and run docker-compose up -d # View logs docker-compose logs -f nextjs # Rebuild docker-compose up -d --build ``` ## Production Optimization ### next.config.js ```js /** @type {import('next').NextConfig} */ const nextConfig = { // Standalone for self-hosting output: "standalone", // Image optimization images: { formats: ["image/avif", "image/webp"], remotePatterns: [ { protocol: "https", hostname: "cdn.example.com", pathname: "/images/**", }, ], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, // Compression compress: true, // Security headers async headers() { return [ { source: "/:path*", headers: [ { key: "X-DNS-Prefetch-Control", value: "on", }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, { key: "X-Frame-Options", value: "SAMEORIGIN", }, { key: "X-Content-Type-Options", value: "nosniff", }, { key: "X-XSS-Protection", value: "1; mode=block", }, { key: "Referrer-Policy", value: "origin-when-cross-origin", }, ], }, ] }, // Experimental features experimental: { optimizePackageImports: ["@mui/material", "lodash"], }, // Bundle analyzer webpack: (config, { isServer }) => { if (process.env.ANALYZE === "true") { const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer") config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: "static", reportFilename: isServer ? "../analyze/server.html" : "./analyze/client.html", }) ) } return config }, } module.exports = nextConfig ``` ### Bundle Analysis ```bash # Install analyzer npm install -D @next/bundle-analyzer # Analyze ANALYZE=true npm run build # Or use built-in npm run build -- --experimental-build-mode=compile ``` ### Performance Monitoring ```tsx // app/layout.tsx import { SpeedInsights } from "@vercel/speed-insights/next" import { Analytics } from "@vercel/analytics/react" export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ## CDN & Edge ### Static Asset CDN ```js // next.config.js const nextConfig = { assetPrefix: process.env.NODE_ENV === "production" ? "https://cdn.example.com" : "", } ``` ### Edge Runtime ```tsx // app/api/edge/route.ts export const runtime = "edge" export async function GET(request: Request) { return new Response("Hello from Edge!", { status: 200, headers: { "content-type": "text/plain", }, }) } // app/page.tsx export const runtime = "edge" export default async function Page() { return
Edge-rendered page
} ``` ## Caching Strategy ### ISR (Incremental Static Regeneration) ```tsx // app/blog/[slug]/page.tsx export const revalidate = 3600 // Revalidate every hour export default async function BlogPost({ params }: { params: { slug: string } }) { const post = await fetchPost(params.slug) return
{post.content}
} ``` ### On-Demand Revalidation ```tsx // app/api/revalidate/route.ts import { revalidatePath } from "next/cache" import { NextRequest } from "next/server" export async function POST(request: NextRequest) { const secret = request.nextUrl.searchParams.get("secret") if (secret !== process.env.REVALIDATE_SECRET) { return Response.json({ message: "Invalid secret" }, { status: 401 }) } const path = request.nextUrl.searchParams.get("path") || "/" revalidatePath(path) return Response.json({ revalidated: true, now: Date.now() }) } ``` ## Database Connection Pooling ```ts // lib/db.ts import { PrismaClient } from "@prisma/client" const globalForPrisma = global as unknown as { prisma: PrismaClient | undefined } export const db = globalForPrisma.prisma ?? new PrismaClient({ log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], }) if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db ``` ## Health Check Endpoint ```tsx // app/api/health/route.ts import { db } from "@/lib/db" export async function GET() { try { // Check database connection await db.$queryRaw`SELECT 1` return Response.json({ status: "ok", timestamp: new Date().toISOString(), uptime: process.uptime(), }) } catch (error) { return Response.json( { status: "error", message: "Database connection failed", }, { status: 503 } ) } } ``` ## CI/CD with GitHub Actions ```yaml # .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: "20" 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 }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: "--prod" ``` ## Monitoring & Logging ```tsx // app/error.tsx "use client" import * as Sentry from "@sentry/nextjs" import { useEffect } from "react" export default function Error({ error }: { error: Error & { digest?: string } }) { useEffect(() => { Sentry.captureException(error) }, [error]) return
Something went wrong!
} ``` ## Quick Reference | Platform | Best For | Effort | | ----------- | -------------------------------- | ------ | | **Vercel** | Zero-config, optimal performance | Low | | **Netlify** | Alternative to Vercel | Low | | **Railway** | Simple hosting with databases | Medium | | **AWS/GCP** | Enterprise, custom needs | High | | **Docker** | Self-hosting, full control | High | ## Production Checklist - [ ] Enable TypeScript strict mode - [ ] Configure CSP headers - [ ] Setup error monitoring (Sentry) - [ ] Configure analytics (Vercel/GA) - [ ] Optimize images (next/image) - [ ] Enable compression - [ ] Setup CDN for static assets - [ ] Configure database connection pooling - [ ] Add health check endpoint - [ ] Setup CI/CD pipeline - [ ] Configure environment variables - [ ] Enable ISR/SSG where possible - [ ] Test Core Web Vitals - [ ] Setup logging (Datadog/LogRocket) - [ ] Configure backup strategy