Deployment and Production Strategies
1. Hosting Platforms (Vercel, Netlify, Cloudflare Pages, AWS Amplify)
Deploy frontend applications to modern hosting platforms with automatic builds, CDN distribution, and serverless functions.
| Platform | Best For | Key Features | Pricing |
|---|---|---|---|
| Vercel | Next.js, React apps | Edge network, ISR, serverless | Free hobby, $20+/mo pro |
| Netlify | Static sites, Jamstack | Form handling, split testing | Free starter, $19+/mo pro |
| Cloudflare Pages | Static sites, workers | Unlimited bandwidth, fast CDN | Free unlimited |
| AWS Amplify | Full-stack apps | Backend integration, auth | Pay per build |
| GitHub Pages | Static sites, docs | Free hosting, Jekyll support | Free |
Example: Vercel deployment configuration
// vercel.json
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"routes": [
{
"src": "/api/(.*)",
"dest": "/api/$1"
},
{
"src": "/(.*)",
"dest": "/$1"
}
],
"env": {
"NEXT_PUBLIC_API_URL": "https://api.example.com",
"DATABASE_URL": "@database_url"
},
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
},
{
"source": "/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"redirects": [
{
"source": "/old-page",
"destination": "/new-page",
"permanent": true
}
],
"rewrites": [
{
"source": "/blog/:path*",
"destination": "https://blog.example.com/:path*"
}
]
}
// Deploy via CLI
npm install -g vercel
vercel login
vercel --prod
// Deploy via Git integration (automatic)
// Push to main branch → auto-deploy to production
// Push to other branches → preview deployments
// Environment variables (via Vercel Dashboard or CLI)
vercel env add NEXT_PUBLIC_API_URL production
vercel env add DATABASE_URL production
// Preview URLs for each PR
// https://my-app-git-feature-branch-username.vercel.app
Example: Netlify deployment configuration
// netlify.toml
[build]
base = "/"
publish = "dist"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
NPM_FLAGS = "--legacy-peer-deps"
# Redirect rules
[[redirects]]
from = "/old-url"
to = "/new-url"
status = 301
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"
status = 200 # SPA fallback
# Headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# Functions (serverless)
[functions]
directory = "netlify/functions"
node_bundler = "esbuild"
# Dev server
[dev]
command = "npm run dev"
port = 3000
publish = "dist"
# Deploy contexts
[context.production]
environment = { NODE_ENV = "production" }
[context.deploy-preview]
environment = { NODE_ENV = "staging" }
[context.branch-deploy]
environment = { NODE_ENV = "development" }
# Form handling (Netlify feature)
# In HTML:
<form name="contact" method="POST" data-netlify="true">
<input type="text" name="name" />
<input type="email" name="email" />
<button type="submit">Submit</button>
</form>
# Deploy via CLI
npm install -g netlify-cli
netlify login
netlify init
netlify deploy --prod
Example: Cloudflare Pages deployment
// wrangler.toml (for Pages with Functions)
name = "my-app"
compatibility_date = "2024-01-01"
[build]
command = "npm run build"
cwd = "."
watch_dir = "src"
[[build.upload]]
format = "service-worker"
main = "./functions/[[path]].js"
[env.production]
[env.production.vars]
API_URL = "https://api.example.com"
# _redirects file (in public folder)
/old-url /new-url 301
/api/* https://api.example.com/:splat 200
/* /index.html 200
# _headers file
/*
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
/static/*
Cache-Control: public, max-age=31536000, immutable
# Cloudflare Pages Function
// functions/api/hello.ts
export async function onRequest(context) {
const { request, env, params } = context;
return new Response(JSON.stringify({
message: 'Hello from Cloudflare Pages',
time: new Date().toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
});
}
# Deploy via CLI
npm install -g wrangler
wrangler login
wrangler pages publish dist --project-name=my-app
# Or use Git integration (automatic)
# Connect GitHub repo → automatic deployments
2. Docker Containers for Frontend Apps (Dockerfile, docker-compose)
Containerize frontend applications with Docker for consistent deployments across environments and orchestration with Kubernetes.
| Strategy | Use Case | Benefits | Complexity |
|---|---|---|---|
| Multi-stage Build | Production optimization | Smaller images, faster builds | Medium |
| Nginx Serving | Static files, reverse proxy | Fast, efficient, caching | Low |
| Node.js SSR | Next.js, server rendering | Dynamic content, APIs | Medium |
| Docker Compose | Multi-container setup | Local dev, testing | Medium |
Example: Multi-stage Dockerfile for React app
# Dockerfile - Multi-stage build for production
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && \
npm cache clean --force
# Copy source code
COPY . .
# Build app
RUN npm run build
# Stage 2: Production
FROM nginx:alpine AS production
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets from builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Add healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
# Build and run
# docker build -t my-app:latest .
# docker run -p 8080:80 my-app:latest
Example: Nginx configuration for SPA
# nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/javascript application/json;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# API proxy (optional)
location /api {
proxy_pass http://backend-service:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# SPA fallback - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Example: Next.js SSR Dockerfile
# Dockerfile for Next.js
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build Next.js app
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set correct permissions
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
# next.config.js - Enable standalone output
module.exports = {
output: 'standalone'
}
Example: Docker Compose for full-stack development
# docker-compose.yml
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
environment:
- REACT_APP_API_URL=http://localhost:4000
depends_on:
- backend
networks:
- app-network
volumes:
- ./frontend/src:/app/src # Hot reload in dev
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- NODE_ENV=development
depends_on:
- db
networks:
- app-network
db:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- frontend
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres-data:
# Commands
# docker-compose up -d
# docker-compose down
# docker-compose logs -f frontend
# docker-compose exec frontend sh
3. CDN and Asset Optimization (CloudFront, Cloudflare CDN)
Distribute static assets globally with CDN for faster load times, reduced latency, and improved user experience worldwide.
| CDN Provider | Key Features | Best For | Pricing Model |
|---|---|---|---|
| CloudFront (AWS) | Global edge, Lambda@Edge | AWS ecosystem integration | Pay per request |
| Cloudflare | DDoS protection, workers | Security, free tier | Free + paid plans |
| Fastly | Instant purge, VCL | Real-time apps | Pay as you go |
| Akamai | Largest network | Enterprise scale | Enterprise pricing |
Example: CloudFront distribution setup with Terraform
# main.tf - CloudFront + S3 setup
resource "aws_s3_bucket" "frontend" {
bucket = "my-app-frontend"
}
resource "aws_s3_bucket_public_access_block" "frontend" {
bucket = aws_s3_bucket.frontend.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_cloudfront_origin_access_identity" "frontend" {
comment = "OAI for frontend bucket"
}
resource "aws_s3_bucket_policy" "frontend" {
bucket = aws_s3_bucket.frontend.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontAccess"
Effect = "Allow"
Principal = {
AWS = aws_cloudfront_origin_access_identity.frontend.iam_arn
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.frontend.arn}/*"
}
]
})
}
resource "aws_cloudfront_distribution" "frontend" {
enabled = true
is_ipv6_enabled = true
comment = "Frontend distribution"
default_root_object = "index.html"
price_class = "PriceClass_100"
aliases = ["www.example.com", "example.com"]
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "S3-Frontend"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-Frontend"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
compress = true
}
# Cache behavior for static assets
ordered_cache_behavior {
path_pattern = "/static/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-Frontend"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 31536000
default_ttl = 31536000
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
# SPA fallback
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/index.html"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = {
Environment = "production"
}
}
Example: CDN cache control headers
// Cache control strategy for different asset types
// 1. HTML files - No cache (always fresh)
Cache-Control: no-cache, no-store, must-revalidate
// Or short cache with revalidation
Cache-Control: max-age=0, must-revalidate
// 2. JavaScript/CSS with hash in filename
// main.a1b2c3d4.js, styles.e5f6g7h8.css
Cache-Control: public, max-age=31536000, immutable
// 3. Images, fonts with hash
// logo.i9j0k1l2.png
Cache-Control: public, max-age=31536000, immutable
// 4. API responses
Cache-Control: max-age=60, s-maxage=300, stale-while-revalidate=86400
// 5. Dynamic content
Cache-Control: private, max-age=0, must-revalidate
// Next.js automatic cache headers
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate'
}
]
},
{
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable'
}
]
},
{
source: '/images/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=86400, s-maxage=604800'
}
]
}
];
}
};
// Webpack output with content hash
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js'
}
// CloudFront cache invalidation
aws cloudfront create-invalidation \
--distribution-id E1234ABCD5678 \
--paths "/*"
// Selective invalidation (cheaper)
aws cloudfront create-invalidation \
--distribution-id E1234ABCD5678 \
--paths "/index.html" "/manifest.json"
Example: Cloudflare Workers for edge optimization
// workers/optimize-images.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Check if WebP is supported
const acceptHeader = request.headers.get('Accept') || '';
const supportsWebP = acceptHeader.includes('image/webp');
// Transform image format at edge
if (url.pathname.match(/\.(jpg|png)$/)) {
const imageRequest = new Request(request);
if (supportsWebP) {
// Serve WebP version
const webpUrl = url.pathname.replace(/\.(jpg|png)$/, '.webp');
return fetch(new Request(webpUrl, imageRequest));
}
}
// Add custom headers
const response = await fetch(request);
const newResponse = new Response(response.body, response);
// Cache static assets aggressively
if (url.pathname.startsWith('/static/')) {
newResponse.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
}
// Security headers
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
newResponse.headers.set('X-Frame-Options', 'DENY');
return newResponse;
}
};
// wrangler.toml
name = "image-optimizer"
main = "workers/optimize-images.js"
compatibility_date = "2024-01-01"
[env.production]
route = "example.com/*"
4. Environment Variables (.env, process.env) and Configuration
Manage environment-specific configuration securely across development, staging, and production environments.
| Approach | Use Case | Security | Complexity |
|---|---|---|---|
| .env Files | Local development | Low (gitignored) | Low |
| Platform Secrets | Vercel/Netlify secrets | High (encrypted) | Low |
| AWS Secrets Manager | Production secrets | High (encrypted, rotated) | Medium |
| HashiCorp Vault | Enterprise secrets | Very High | High |
Example: Environment variables setup with validation
// .env.example (commit to git)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
DATABASE_URL=postgresql://localhost:5432/mydb
JWT_SECRET=your-secret-key-here
STRIPE_SECRET_KEY=sk_test_xxx
// .env.local (gitignored - actual values)
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_GA_ID=G-123456789
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb_dev
JWT_SECRET=super-secret-dev-key
STRIPE_SECRET_KEY=sk_test_actual_key
// .env.production (production values - use secrets manager)
NEXT_PUBLIC_API_URL=https://api.production.com
NEXT_PUBLIC_GA_ID=G-PROD123456
DATABASE_URL=@database-url-secret
JWT_SECRET=@jwt-secret
STRIPE_SECRET_KEY=@stripe-secret
// .gitignore
.env*.local
.env.production
// config/env.ts - Type-safe environment variables
import { z } from 'zod';
const envSchema = z.object({
// Public variables (available in browser)
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_GA_ID: z.string().optional(),
// Server-only variables
DATABASE_URL: z.string(),
JWT_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
// Environment
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development')
});
// Validate and export
const _env = envSchema.safeParse(process.env);
if (!_env.success) {
console.error('❌ Invalid environment variables:', _env.error.format());
throw new Error('Invalid environment variables');
}
export const env = _env.data;
// Usage
import { env } from '@/config/env';
console.log(env.NEXT_PUBLIC_API_URL); // Type-safe access
console.log(env.JWT_SECRET); // Only available server-side
Example: Multiple environment configurations
// config/environments.ts
interface AppConfig {
apiUrl: string;
environment: 'development' | 'staging' | 'production';
features: {
analytics: boolean;
errorTracking: boolean;
debugMode: boolean;
};
endpoints: {
api: string;
auth: string;
cdn: string;
};
}
const development: AppConfig = {
apiUrl: 'http://localhost:4000',
environment: 'development',
features: {
analytics: false,
errorTracking: false,
debugMode: true
},
endpoints: {
api: 'http://localhost:4000/api',
auth: 'http://localhost:4000/auth',
cdn: 'http://localhost:3000'
}
};
const staging: AppConfig = {
apiUrl: 'https://api-staging.example.com',
environment: 'staging',
features: {
analytics: true,
errorTracking: true,
debugMode: true
},
endpoints: {
api: 'https://api-staging.example.com/api',
auth: 'https://api-staging.example.com/auth',
cdn: 'https://cdn-staging.example.com'
}
};
const production: AppConfig = {
apiUrl: 'https://api.example.com',
environment: 'production',
features: {
analytics: true,
errorTracking: true,
debugMode: false
},
endpoints: {
api: 'https://api.example.com/api',
auth: 'https://api.example.com/auth',
cdn: 'https://cdn.example.com'
}
};
const configs = { development, staging, production };
export const config = configs[process.env.NODE_ENV || 'development'];
// Feature flags
export const isProduction = config.environment === 'production';
export const isDevelopment = config.environment === 'development';
export const isStaging = config.environment === 'staging';
// Usage
import { config, isProduction } from '@/config/environments';
if (isProduction) {
initAnalytics(config.endpoints.api);
}
Example: AWS Secrets Manager integration
// Installation
npm install @aws-sdk/client-secrets-manager
// lib/secrets.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({
region: process.env.AWS_REGION || 'us-east-1'
});
export async function getSecret(secretName: string): Promise<any> {
try {
const command = new GetSecretValueCommand({
SecretId: secretName
});
const response = await client.send(command);
if (response.SecretString) {
return JSON.parse(response.SecretString);
}
// Binary secret
const buff = Buffer.from(response.SecretBinary as Uint8Array);
return buff.toString('ascii');
} catch (error) {
console.error('Error retrieving secret:', error);
throw error;
}
}
// Cache secrets in memory
const secretsCache = new Map<string, any>();
export async function getCachedSecret(secretName: string): Promise<any> {
if (secretsCache.has(secretName)) {
return secretsCache.get(secretName);
}
const secret = await getSecret(secretName);
secretsCache.set(secretName, secret);
// Refresh after 1 hour
setTimeout(() => {
secretsCache.delete(secretName);
}, 60 * 60 * 1000);
return secret;
}
// Usage in Next.js API route
export async function GET() {
const secrets = await getCachedSecret('prod/api/keys');
const response = await fetch(secrets.API_URL, {
headers: {
'Authorization': `Bearer ${secrets.API_KEY}`
}
});
return Response.json(await response.json());
}
// Terraform to create secret
resource "aws_secretsmanager_secret" "api_keys" {
name = "prod/api/keys"
description = "API keys for production"
}
resource "aws_secretsmanager_secret_version" "api_keys" {
secret_id = aws_secretsmanager_secret.api_keys.id
secret_string = jsonencode({
API_URL = "https://api.example.com"
API_KEY = "secret-key-value"
STRIPE_KEY = "sk_live_xxx"
})
}
5. Deployment Strategies (Blue-Green, Canary, Rolling Deployments)
Implement zero-downtime deployment strategies with instant rollback capabilities and gradual traffic shifting.
| Strategy | Approach | Risk | Rollback Time |
|---|---|---|---|
| Blue-Green | Switch all traffic instantly | Medium | Instant |
| Canary | Gradual traffic shift (5%→50%→100%) | Low | Quick |
| Rolling Update | Replace instances one by one | Low | Minutes |
| A/B Testing | Split traffic by user segment | Low | N/A (experimental) |
Example: Kubernetes Blue-Green deployment
# blue-deployment.yaml (current production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-blue
labels:
app: frontend
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: frontend
version: blue
template:
metadata:
labels:
app: frontend
version: blue
spec:
containers:
- name: frontend
image: myapp/frontend:v1.0.0
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
# green-deployment.yaml (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-green
labels:
app: frontend
version: green
spec:
replicas: 3
selector:
matchLabels:
app: frontend
version: green
template:
metadata:
labels:
app: frontend
version: green
spec:
containers:
- name: frontend
image: myapp/frontend:v2.0.0 # New version
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
version: blue # Initially pointing to blue
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
# Deployment process:
# 1. Deploy green version
kubectl apply -f green-deployment.yaml
# 2. Test green version internally
kubectl port-forward deployment/frontend-green 8080:80
# 3. Switch traffic to green (instant cutover)
kubectl patch service frontend-service -p '{"spec":{"selector":{"version":"green"}}}'
# 4. Monitor for issues
kubectl logs -f deployment/frontend-green
# 5. If issues, rollback to blue instantly
kubectl patch service frontend-service -p '{"spec":{"selector":{"version":"blue"}}}'
# 6. If successful, delete blue deployment
kubectl delete deployment frontend-blue
Example: Canary deployment with traffic splitting
# Using Istio for canary deployment
# virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: frontend
spec:
hosts:
- frontend.example.com
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: frontend
subset: v2
- route:
- destination:
host: frontend
subset: v1
weight: 95
- destination:
host: frontend
subset: v2
weight: 5 # 5% traffic to canary
---
# destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: frontend
spec:
host: frontend
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
# Canary deployment script
#!/bin/bash
# Stage 1: Deploy v2 with 5% traffic
kubectl apply -f canary-deployment.yaml
kubectl apply -f virtual-service-5.yaml
# Wait and monitor
sleep 300
# Stage 2: Increase to 25%
kubectl apply -f virtual-service-25.yaml
sleep 300
# Stage 3: Increase to 50%
kubectl apply -f virtual-service-50.yaml
sleep 300
# Stage 4: Increase to 100%
kubectl apply -f virtual-service-100.yaml
# Remove old version
kubectl delete deployment frontend-v1
# Using Flagger for automated canary
# flagger-canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: frontend
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend
service:
port: 80
analysis:
interval: 1m
threshold: 10
maxWeight: 50
stepWeight: 5
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester/
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://frontend-canary/"
Example: Vercel deployment with preview URLs
// Vercel automatic preview deployments
// Every PR gets a unique preview URL
// .github/workflows/preview.yml
name: Preview Deployment
on:
pull_request:
branches: [main]
jobs:
deploy-preview:
runs-on: ubuntu-latest
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 }}
scope: ${{ secrets.VERCEL_SCOPE }}
- name: Comment PR
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 Preview deployed to: https://my-app-pr-${{ github.event.number }}.vercel.app'
})
// Netlify branch deploys
// netlify.toml
[context.deploy-preview]
command = "npm run build"
environment = { NODE_ENV = "staging" }
[context.branch-deploy]
command = "npm run build"
# Each branch gets: https://branch-name--my-app.netlify.app
# Each PR gets: https://deploy-preview-123--my-app.netlify.app
// Progressive rollout with feature flags
import { useFeatureFlag } from '@/lib/feature-flags';
function NewFeature() {
const isEnabled = useFeatureFlag('new-ui-redesign', {
rollout: 10 // Start with 10% of users
});
if (!isEnabled) {
return <OldFeature />;
}
return <RedesignedFeature />;
}
// Gradually increase rollout
// Day 1: 10%
// Day 3: 25%
// Day 7: 50%
// Day 14: 100%
6. Monitoring and Performance Tracking (Sentry, New Relic, Datadog)
Implement comprehensive monitoring and error tracking to detect issues early and maintain application health in production.
| Tool | Purpose | Key Features | Pricing |
|---|---|---|---|
| Sentry | Error tracking, performance | Source maps, releases, alerts | Free + paid tiers |
| Datadog | Full observability | APM, logs, metrics, traces | $15+/host/month |
| New Relic | Application monitoring | Real user monitoring (RUM) | Free + paid |
| LogRocket | Session replay | Video replay, console logs | $99+/month |
| Google Analytics 4 | User analytics | Events, conversions, funnels | Free |
Example: Sentry integration with source maps
// Installation
npm install @sentry/nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
// Performance Monitoring
tracesSampleRate: 1.0,
// Session Replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
new Sentry.BrowserTracing({
tracePropagationTargets: [
'localhost',
/^https:\/\/api\.example\.com/
]
}),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true
})
],
// Filter sensitive data
beforeSend(event, hint) {
// Remove PII
if (event.request) {
delete event.request.cookies;
delete event.request.headers;
}
// Ignore certain errors
if (event.exception) {
const error = hint.originalException;
if (error && error.message?.includes('ResizeObserver')) {
return null; // Don't send this error
}
}
return event;
}
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0
});
// next.config.js - Source maps
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
{
// Your Next.js config
},
{
silent: true,
org: 'my-org',
project: 'my-project',
// Upload source maps
authToken: process.env.SENTRY_AUTH_TOKEN,
// Release tracking
release: process.env.VERCEL_GIT_COMMIT_SHA,
// Source map options
widenClientFileUpload: true,
hideSourceMaps: true,
disableLogger: true
}
);
// Usage in components
import * as Sentry from '@sentry/nextjs';
function MyComponent() {
const handleError = () => {
try {
throw new Error('Something went wrong');
} catch (error) {
Sentry.captureException(error, {
tags: {
component: 'MyComponent',
action: 'button-click'
},
extra: {
userId: user.id,
timestamp: Date.now()
}
});
}
};
return <button onClick={handleError}>Trigger Error</button>;
}
Example: Custom performance monitoring
// lib/monitoring.ts
import * as Sentry from '@sentry/nextjs';
// Track custom metrics
export function trackMetric(name: string, value: number, tags?: Record<string, string>) {
Sentry.metrics.gauge(name, value, {
tags,
unit: 'millisecond'
});
}
// Track API performance
export async function monitoredFetch(url: string, options?: RequestInit) {
const startTime = performance.now();
const transaction = Sentry.startTransaction({
name: `API ${options?.method || 'GET'} ${url}`,
op: 'http.client'
});
try {
const response = await fetch(url, options);
const duration = performance.now() - startTime;
// Track success
trackMetric('api.response_time', duration, {
endpoint: url,
status: String(response.status),
method: options?.method || 'GET'
});
if (!response.ok) {
Sentry.captureMessage(`API Error: ${response.status} ${url}`, 'warning');
}
transaction.setHttpStatus(response.status);
transaction.finish();
return response;
} catch (error) {
const duration = performance.now() - startTime;
// Track failure
trackMetric('api.error_count', 1, {
endpoint: url,
error: error.message
});
Sentry.captureException(error, {
tags: { endpoint: url },
extra: { duration }
});
transaction.setStatus('internal_error');
transaction.finish();
throw error;
}
}
// React Error Boundary with Sentry
import { ErrorBoundary } from '@sentry/nextjs';
function App() {
return (
<ErrorBoundary
fallback={({ error, resetError }) => (
<div>
<h1>Something went wrong</h1>
<p>{error.message}</p>
<button onClick={resetError}>Try again</button>
</div>
)}
showDialog
>
<YourApp />
</ErrorBoundary>
);
}
// Core Web Vitals tracking
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to Sentry
Sentry.metrics.gauge(metric.name, metric.value, {
tags: {
rating: metric.rating
},
unit: metric.name === 'CLS' ? 'ratio' : 'millisecond'
});
// Send to Google Analytics
gtag('event', metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_rating: metric.rating
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
Example: Comprehensive monitoring dashboard setup
// Datadog RUM integration
import { datadogRum } from '@datadog/browser-rum';
datadogRum.init({
applicationId: process.env.NEXT_PUBLIC_DD_APP_ID!,
clientToken: process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN!,
site: 'datadoghq.com',
service: 'my-app',
env: process.env.NODE_ENV,
version: process.env.NEXT_PUBLIC_APP_VERSION,
sessionSampleRate: 100,
sessionReplaySampleRate: 20,
trackUserInteractions: true,
trackResources: true,
trackLongTasks: true,
defaultPrivacyLevel: 'mask-user-input'
});
// Custom monitoring service
class MonitoringService {
private static instance: MonitoringService;
private constructor() {
this.initHealthChecks();
}
static getInstance() {
if (!MonitoringService.instance) {
MonitoringService.instance = new MonitoringService();
}
return MonitoringService.instance;
}
// Track page views
trackPageView(page: string) {
gtag('event', 'page_view', { page_path: page });
Sentry.addBreadcrumb({
category: 'navigation',
message: `Navigated to ${page}`,
level: 'info'
});
}
// Track custom events
trackEvent(name: string, properties?: Record<string, any>) {
gtag('event', name, properties);
Sentry.addBreadcrumb({
category: 'user-action',
message: name,
data: properties,
level: 'info'
});
}
// Track errors
trackError(error: Error, context?: Record<string, any>) {
console.error(error);
Sentry.captureException(error, {
extra: context
});
}
// Health checks
private initHealthChecks() {
setInterval(() => {
this.checkApiHealth();
this.checkPerformance();
}, 60000); // Every minute
}
private async checkApiHealth() {
try {
const response = await fetch('/api/health');
if (!response.ok) {
this.trackError(new Error('API health check failed'), {
status: response.status
});
}
} catch (error) {
this.trackError(error as Error, { check: 'api-health' });
}
}
private checkPerformance() {
const memory = (performance as any).memory;
if (memory && memory.usedJSHeapSize > 100 * 1024 * 1024) {
Sentry.captureMessage('High memory usage detected', {
level: 'warning',
extra: {
usedHeapSize: memory.usedJSHeapSize,
totalHeapSize: memory.totalJSHeapSize
}
});
}
}
}
export const monitoring = MonitoringService.getInstance();
// Usage
monitoring.trackEvent('button_click', { button: 'checkout' });
monitoring.trackError(new Error('Payment failed'), { amount: 99.99 });
Production Deployment Checklist
| Category | Task | Priority | Status |
|---|---|---|---|
| Hosting | Configure CDN and caching | High | ☐ |
| Security | Set up HTTPS and security headers | Critical | ☐ |
| Monitoring | Integrate error tracking (Sentry) | Critical | ☐ |
| Performance | Track Core Web Vitals | High | ☐ |
| CI/CD | Automated deployment pipeline | High | ☐ |
| Config | Environment variables properly set | Critical | ☐ |
| Deployment | Blue-green or canary strategy | Medium | ☐ |
| Backup | Rollback plan documented | High | ☐ |