Deployment & Production
Build optimization, deployment strategies, hosting platforms, and production considerations for Next.js applications.
Build and Production
Build Commands
# Development build
npm run dev
# Production build
npm run build
# Start production server
npm run start
# Export static site
npm run build && npm run export
# Analyze bundle
npm run build -- --analyze
Build Configuration
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Build output
output: 'standalone', // or 'export'
// Compress output
compress: true,
// Generate ETags
generateEtags: true,
// Optimize images
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60,
},
// Experimental features
experimental: {
optimizeCss: true,
optimizePackageImports: ['lodash', 'date-fns'],
},
// Custom webpack config
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Production optimizations
if (!dev && !isServer) {
config.resolve.alias = {
...config.resolve.alias,
react: 'preact/compat',
'react-dom': 'preact/compat',
};
}
return config;
},
// Environment variables
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
// Headers
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
],
},
];
},
};
module.exports = nextConfig;
Environment Variables
# .env.local (development)
NEXT_PUBLIC_API_URL=http://localhost:3000/api
DATABASE_URL=postgresql://localhost:5432/myapp_dev
SECRET_KEY=dev-secret-key
# .env.production (production)
NEXT_PUBLIC_API_URL=https://api.myapp.com
DATABASE_URL=postgresql://prod-server:5432/myapp_prod
SECRET_KEY=prod-secret-key
# .env (all environments)
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_VERSION=1.0.0
// app/lib/config.ts
export const config = {
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
databaseUrl: process.env.DATABASE_URL!,
secretKey: process.env.SECRET_KEY!,
appName: process.env.NEXT_PUBLIC_APP_NAME!,
version: process.env.NEXT_PUBLIC_VERSION!,
isDev: process.env.NODE_ENV === 'development',
isProd: process.env.NODE_ENV === 'production',
};
Hosting Platforms
Vercel Deployment
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel
# Deploy to production
vercel --prod
# Set environment variables
vercel env add SECRET_KEY
vercel env add DATABASE_URL
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1", "sfo1"],
"functions": {
"app/api/**/*.ts": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
}
]
}
],
"rewrites": [
{
"source": "/api/(.*)",
"destination": "/api/$1"
}
]
}
Netlify Deployment
# Install Netlify CLI
npm install -g netlify-cli
# Deploy
netlify deploy
# Deploy to production
netlify deploy --prod
# netlify.toml
[build]
command = "npm run build"
publish = ".next"
[build.environment]
NODE_VERSION = "18"
NPM_VERSION = "9"
[[plugins]]
package = "@netlify/plugin-nextjs"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
[[redirects]]
from = "/old-page"
to = "/new-page"
status = 301
AWS Amplify
# amplify.yml
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
Docker Deployment
# Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Production image, copy all the files and run next
FROM base 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 /app/public ./public
# Automatically leverage output traces to reduce image size
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
# 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/myapp
depends_on:
- db
restart: unless-stopped
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
Performance Monitoring
Analytics Integration
// app/lib/analytics.ts
declare global {
interface Window {
gtag: (command: string, ...args: any[]) => void;
}
}
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
export function pageview(url: string) {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('config', GA_TRACKING_ID, {
page_path: url,
});
}
}
export function event(
action: string,
category: string,
label?: string,
value?: number
) {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
}
}
// app/components/Analytics.tsx
'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { pageview } from '@/lib/analytics';
export default function Analytics() {
const pathname = usePathname();
useEffect(() => {
pageview(pathname);
}, [pathname]);
return null;
}
Error Tracking
// app/lib/errorTracking.ts
export function logError(error: Error, context?: any) {
// Log to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error:', error);
console.error('Context:', context);
}
// Send to error tracking service
if (typeof window !== 'undefined') {
// Sentry, LogRocket, etc.
// Sentry.captureException(error, { extra: context });
}
}
export function logEvent(event: string, data?: any) {
// Log to analytics service
if (typeof window !== 'undefined') {
// analytics.track(event, data);
}
}
Security Considerations
Content Security Policy
// app/lib/csp.ts
export function generateCSP() {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`font-src 'self'`,
`connect-src 'self' https://api.example.com`,
`frame-src 'none'`,
`object-src 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
`frame-ancestors 'none'`,
].join('; ');
return { csp, nonce };
}
Security Headers
// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
];
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
CI/CD Pipeline
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
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: Run linting
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build application
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- 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'
Docker CI/CD
# .github/workflows/docker.yml
name: Docker Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Database and Storage
Database Connection
// app/lib/db.ts
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl:
process.env.NODE_ENV === 'production'
? { rejectUnauthorized: false }
: false,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
export async function query(text: string, params?: any[]) {
const client = await pool.connect();
try {
const result = await client.query(text, params);
return result;
} finally {
client.release();
}
}
File Storage
// app/lib/storage.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export async function uploadFile(file: File, key: string) {
const buffer = await file.arrayBuffer();
const command = new PutObjectCommand({
Bucket: process.env.AWS_BUCKET_NAME,
Key: key,
Body: buffer,
ContentType: file.type,
});
return await s3Client.send(command);
}
Health Checks and Monitoring
Health Check Endpoint
// app/api/health/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
try {
// Check database connection
// Check external APIs
// Check file system
return NextResponse.json({
status: 'healthy',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version,
});
} catch (error) {
return NextResponse.json(
{
status: 'unhealthy',
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
},
{ status: 503 }
);
}
}
Logging
// app/lib/logger.ts
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport:
process.env.NODE_ENV === 'development'
? {
target: 'pino-pretty',
options: {
colorize: true,
},
}
: undefined,
});
export default logger;
Quick Reference
Build Commands
npm run build- Production buildnpm run start- Start production servernpm run export- Export static sitenpm run analyze- Analyze bundle size
Environment Variables
NEXT_PUBLIC_*- Client-side accessibleNODE_ENV- Environment (development/production)DATABASE_URL- Database connectionSECRET_KEY- Server-side secrets
Deployment Platforms
- Vercel: Native Next.js platform
- Netlify: JAMstack platform
- AWS Amplify: Full-stack platform
- Docker: Container deployment
Performance Monitoring
- Core Web Vitals tracking
- Error boundaries and logging
- Analytics integration
- Health check endpoints
Security Headers
- Content Security Policy (CSP)
- X-Frame-Options
- X-Content-Type-Options
- Strict-Transport-Security
Best Practices
- Use environment variables for configuration
- Implement proper error handling
- Set up monitoring and logging
- Use CDN for static assets
- Implement health checks
- Use SSL/TLS in production
- Regular security updates
- Monitor performance metrics