Modern Deployment Delivery Implementation
1. Vercel Netlify Jamstack Deployment
| Platform | Features | Configuration | Best Practices |
|---|---|---|---|
| Vercel NEW | Zero-config, Edge Functions, Preview URLs, Analytics, DDoS protection, 100+ global edge | vercel.json: builds, routes, headers, redirects, env vars. Auto-detect framework |
Optimized for Next.js. Free 100GB bandwidth. Use Edge Config for feature flags. ISR for dynamic |
| Netlify | Edge Functions, Split Testing, Form handling, Identity auth, Deploy previews, Rollbacks | netlify.toml: build command, publish dir, redirects, headers, plugins, functions |
Free 100GB bandwidth. Use Netlify Functions for serverless. Deploy contexts (prod/preview) |
| Jamstack Architecture | Pre-rendered HTML, API-driven, Git-based workflow, CDN distribution, Decoupled frontend | SSG with Next/Gatsby/Astro. APIs for dynamic data. Headless CMS. Build-time data fetching | 99.99% uptime. 10x faster than SSR. SEO-optimized. Use webhooks for content updates |
| Build Optimization | Incremental builds, Build cache, Parallel builds, Dependency caching, On-demand ISR | Cache node_modules, .next, .cache. Turbopack (Vercel). Use
buildCommand |
Reduce build time 50-80%. Cache restoration 10-30s. Parallel builds for monorepos |
| Deploy Previews | PR preview URLs, Branch deploys, Unique URLs per commit, Shareable links, E2E testing | Auto-deploy on PR. Comment preview URL. Delete on merge. Use for QA testing | Test before production. Share with stakeholders. Run E2E tests on preview. Instant rollback |
| Edge Functions | Serverless at edge, Low latency, Auto-scaling, A/B testing, Geo-location, Middleware | Vercel Edge: /api/edge.ts. Netlify Edge: netlify/edge-functions/ |
Sub-10ms latency. Use for auth, redirects, A/B tests. 50ms execution limit. Lightweight only |
Example: Vercel deployment configuration
// vercel.json
{
"buildCommand": "pnpm build",
"outputDirectory": "dist",
"framework": "vite",
"rewrites": [
{ "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" }
]
}
],
"env": {
"API_URL": "@api_url_production"
}
}
Example: Netlify configuration
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
NPM_FLAGS = "--legacy-peer-deps"
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
[[plugins]]
package = "@netlify/plugin-lighthouse"
2. GitHub Actions CI/CD Pipeline
| Component | Purpose | Configuration | Best Practices |
|---|---|---|---|
| Workflow Triggers | push, pull_request, schedule, workflow_dispatch, release, manual trigger | on: [push, pull_request]. Filter branches: branches: [main] |
Trigger on PR for tests. Deploy on main push. Schedule nightly builds. Use paths filter |
| Build Job | Install deps, lint, test, build, cache dependencies, parallel execution | runs-on: ubuntu-latest. Use actions/setup-node@v4. Cache npm/pnpm |
Cache node_modules (5-10x faster). Use matrix for multi-version. Fail fast on errors |
| Testing Jobs | Unit tests, integration tests, E2E tests, coverage, security scans, lighthouse | Jest/Vitest for unit. Playwright for E2E. Upload coverage to Codecov. Parallel tests | Run unit tests first (fast). E2E on preview deploy. 80%+ coverage. Security scan dependencies |
| Deploy Job | Production deploy, staging deploy, preview environments, rollback capability | needs: [build, test]. Use secrets for credentials. Conditional on branch |
Deploy after tests pass. Use environment protection rules. Auto-rollback on failure |
| Caching Strategy | Dependencies, build artifacts, node_modules, .next cache, test cache | actions/cache@v4 with key: ${{ runner.os }}-node-${{ hashFiles }} |
Cache node_modules (2-5 min → 30s). Restore build cache. Use restore-keys fallback |
| Security & Secrets | Encrypted secrets, OIDC tokens, environment secrets, secret scanning, Dependabot | Store in GitHub Secrets. Use ${{ secrets.API_KEY }}. Never log secrets |
Rotate secrets quarterly. Use OIDC instead of tokens. Enable secret scanning. Audit access |
Example: Complete CI/CD pipeline
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm type-check
- name: Unit tests
run: pnpm test:unit --coverage
- name: Build
run: pnpm build
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
e2e-tests:
needs: build-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm playwright install --with-deps
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
deploy:
needs: [build-and-test, e2e-tests]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
run: vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
3. Docker Container Frontend Apps
| Aspect | Implementation | Configuration | Best Practices |
|---|---|---|---|
| Multi-stage Build | Build stage, Production stage, Nginx serving, Minimize image size, Security hardening | Stage 1: node:20-alpine build. Stage 2: nginx:alpine-slim copy artifacts. 50-100MB final | Use alpine images. Copy only dist/. Remove source maps. Non-root user. Scan vulnerabilities |
| Nginx Configuration | Static file serving, SPA routing, Gzip compression, Cache headers, Security headers | nginx.conf: try_files, gzip on, cache-control, add_header. Port 80/8080 |
Enable gzip (70% reduction). Cache static assets. SPA fallback index.html. HTTPS redirect |
| Build Optimization | Layer caching, .dockerignore, Dependency caching, Parallel builds, Build args | Copy package.json first. Install deps. Copy source. Build. Use BuildKit cache mounts | Cache npm install layer. .dockerignore node_modules. Use --mount=type=cache. 10x faster |
| Environment Variables | Runtime config, Build-time vars, ARG vs ENV, Config injection, 12-factor app | ARG for build. ENV for runtime. Use window.__ENV__ or env.js. No secrets in image |
Inject at runtime (not build). Use ConfigMaps in K8s. Template env.js file. Never commit secrets |
| Health Checks | Container health, Startup probe, Readiness probe, Liveness probe, Graceful shutdown | HEALTHCHECK CMD curl -f http://localhost/ || exit 1. 30s interval |
Return 200 on /health. Check dependencies. Fail if not ready. K8s probes required |
| Security Hardening | Non-root user, Minimal base image, Vulnerability scanning, No secrets, Read-only FS | USER node (1000). RUN chmod. Trivy/Snyk scan. Secrets from vault. COPY --chown | Scan images weekly. Update base images. No RUN as root. Mount secrets. Least privilege |
Example: Production Dockerfile with multi-stage build
# Dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json pnpm-lock.yaml ./
# Install dependencies with cache mount
RUN --mount=type=cache,target=/root/.npm \
npm install -g pnpm && \
pnpm install --frozen-lockfile
# Copy source
COPY . .
# Build application
RUN pnpm build
# Stage 2: Production
FROM nginx:alpine-slim
# Copy nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html
# Create non-root user
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
chown -R appuser:appuser /usr/share/nginx/html && \
chown -R appuser:appuser /var/cache/nginx
# Switch to non-root
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
Example: Nginx configuration for SPA
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 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;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA routing - fallback to index.html
location / {
try_files $uri $uri/ /index.html;
}
# API proxy (optional)
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
4. CloudFront S3 Static Hosting
| Component | Configuration | Features | Best Practices |
|---|---|---|---|
| S3 Bucket Setup | Static website hosting, Bucket policy, CORS config, Versioning, Lifecycle rules | Host index.html, Public read access, Website endpoint, 99.99% durability | Enable versioning. Lifecycle delete old versions. Block public write. Use IAM roles |
| CloudFront CDN | Global edge network, SSL/TLS, Custom domain, Origin access, Cache behavior | 450+ edge locations, Sub-50ms latency, DDoS protection, HTTP/2, Brotli compression | Use CloudFront OAI. Custom error pages. Cache everything. Invalidate on deploy |
| Cache Strategy | Cache-Control headers, TTL settings, Query string caching, Version/hash filenames | Max-age 31536000 for static. No-cache for HTML. ETags for validation | Hash filenames for cache-busting. Cache static 1yr. HTML no-cache. Use CloudFront Functions |
| SSL Certificate | ACM certificate, HTTPS redirect, TLS 1.2+, Custom domain, DNS validation | Free SSL via ACM, Auto-renewal, SNI support, Modern cipher suites | Use us-east-1 for ACM. Enable HTTPS only. HTTP→HTTPS redirect. Enable HSTS |
| Error Handling | Custom error pages, 404 fallback, 403 to index, SPA routing support | Return index.html for 404/403, Custom error pages, Status code mapping | 404→index.html for SPA. Custom 500 page. Monitor 4xx/5xx rates. Log errors to S3 |
| Security Headers | CSP, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy | CloudFront Functions or Lambda@Edge for headers. WAF for DDoS. Geo-blocking | Add security headers via CF Functions. Enable WAF. Block bots. Rate limiting |
Example: Deploy script for S3 + CloudFront
#!/bin/bash
# deploy.sh
# Build application
npm run build
# Sync to S3 with cache headers
aws s3 sync dist/ s3://my-app-bucket/ \
--delete \
--cache-control "max-age=31536000,public,immutable" \
--exclude "index.html" \
--exclude "*.html"
# Upload HTML with no-cache
aws s3 sync dist/ s3://my-app-bucket/ \
--cache-control "no-cache,no-store,must-revalidate" \
--exclude "*" \
--include "*.html"
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id E1234ABCD5678 \
--paths "/*"
echo "Deployment complete!"
Example: CloudFront Functions for security headers
// cloudfront-function.js
function handler(event) {
var response = event.response;
var headers = response.headers;
// Security headers
headers['strict-transport-security'] = {
value: 'max-age=31536000; includeSubDomains; preload'
};
headers['x-content-type-options'] = { value: 'nosniff' };
headers['x-frame-options'] = { value: 'DENY' };
headers['x-xss-protection'] = { value: '1; mode=block' };
headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' };
headers['content-security-policy'] = {
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
};
return response;
}
5. Blue-Green Canary Deployment
| Strategy | Description | Implementation | Use Cases & Benefits |
|---|---|---|---|
| Blue-Green Deployment | Two identical environments. Deploy to green. Switch traffic. Instant rollback | Load balancer routes to blue. Deploy green. Test. Switch DNS/LB. Keep blue for rollback | Zero downtime. Instant rollback. Test production environment. Database challenge |
| Canary Deployment | Gradual rollout. 5%→25%→50%→100% traffic. Monitor metrics. Rollback on issues | Deploy canary. Route 5% traffic. Monitor errors/latency. Increase if healthy. Full rollout | Risk mitigation. Early issue detection. Gradual rollout. Metrics-driven |
| A/B Testing | Multiple versions. Split traffic by user attributes. Compare metrics. Data-driven decisions | Deploy variants. Route by user ID/cookie. Track conversion. Statistical significance | Feature testing. UI experiments. Business metrics. User segmentation |
| Feature Flags | Toggle features runtime. Gradual rollout. Kill switch. No redeployment needed | LaunchDarkly, Unleash, Split. Boolean/percentage rollout. User targeting. Override UI | Decouple deploy from release. Instant kill switch. User targeting. Beta testing |
| Rolling Deployment | Update servers one-by-one. Always available. Gradual migration. Version coexistence | K8s RollingUpdate. Update 25% pods at a time. Health checks. Rollback on failure | No downtime. Incremental. Resource efficient. Slower than blue-green |
| Rollback Strategy | Instant traffic switch. Version pinning. Database migrations. State management | Keep previous version. DNS/LB switch. Feature flag disable. DB backward compatible | Sub-1min rollback. No data loss. Audit trail. Automated rollback on errors |
Example: Kubernetes canary deployment with Flagger
# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: frontend-app
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend-app
service:
port: 80
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
# Traffic routing
canaryAnalysis:
# Start with 10% traffic
# Increase by 10% every minute if healthy
# Rollback if 5 failures
Example: Feature flag implementation
// featureFlags.ts
import { LaunchDarkly } from '@launchdarkly/node-server-sdk';
const ldClient = LaunchDarkly.init(process.env.LD_SDK_KEY);
export async function checkFeature(
userId: string,
flagKey: string
): Promise<boolean> {
const user = { key: userId };
const flagValue = await ldClient.variation(flagKey, user, false);
return flagValue;
}
// React component usage
function NewFeature() {
const { flags } = useLDClient();
if (!flags.newCheckoutFlow) {
return <OldCheckout />;
}
return <NewCheckout />;
}
// Gradual rollout configuration in LaunchDarkly:
// - 0-5%: Internal users only
// - 5-25%: Beta users
// - 25-50%: Random 50% of users
// - 50-100%: All users
// Rollback: Set to 0% instantly
6. Environment Variables Config Management
| Aspect | Implementation | Tools & Patterns | Security & Best Practices |
|---|---|---|---|
| 12-Factor Config | Separate config from code. Store in environment. Different per deploy. No secrets in repo | .env files locally. Platform env vars in prod. Process.env access. Validation at startup | Never commit .env. Use .env.example template. Validate required vars. Fail fast if missing |
| Frontend Env Vars | Build-time injection. Runtime config. Public vs private. VITE_*, NEXT_PUBLIC_*, REACT_APP_* | Vite: VITE_ prefix. Next: NEXT_PUBLIC_. CRA: REACT_APP_. Expose via import.meta.env | Never expose secrets to frontend. Use prefixes for public vars. Runtime config for sensitive |
| Secrets Management | Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Encrypted at rest | Store API keys, DB creds, tokens. Rotate regularly. Access control. Audit logs | Never log secrets. Rotate every 90 days. Least privilege access. Use temporary credentials |
| Multi-Environment | dev, staging, production. Different config per env. Promote config through pipeline | .env.development, .env.production. Platform env vars. Config service. Feature flags | Production parity. Explicit env switching. No dev secrets in prod. Test staging config |
| Validation & Type Safety | Zod/Joi validation. TypeScript env types. Fail at startup. Required vs optional | Zod schema validation. @t3-oss/env-nextjs. Env.d.ts types. Runtime validation | Validate on app start. Type-safe access. Clear error messages. Document all variables |
| Config Injection | Runtime config. window.__ENV__. Config endpoint. Dynamic without rebuild | Serve config.js from server. Template replacement. K8s ConfigMaps. Consul/etcd | No rebuild for config changes. Separate config deployment. Versioned config. Rollback support |
Example: Environment validation with Zod
// env.ts
import { z } from 'zod';
const envSchema = z.object({
// Public variables (exposed to frontend)
VITE_API_URL: z.string().url(),
VITE_APP_ENV: z.enum(['development', 'staging', 'production']),
VITE_SENTRY_DSN: z.string().optional(),
// Server-only variables (not exposed)
DATABASE_URL: z.string().min(1),
API_SECRET_KEY: z.string().min(32),
JWT_SECRET: z.string().min(32),
// Optional with defaults
PORT: z.string().default('3000'),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});
// Validate at startup
const env = envSchema.parse(process.env);
// Type-safe access
export { env };
// Usage
import { env } from './env';
console.log(env.VITE_API_URL); // Type-safe, validated
Example: Runtime config injection for Docker
// public/config.js template
window.__ENV__ = {
API_URL: '${API_URL}',
ENVIRONMENT: '${ENVIRONMENT}',
FEATURE_FLAGS: '${FEATURE_FLAGS}'
};
// entrypoint.sh - Replace at container start
#!/bin/sh
envsubst < /usr/share/nginx/html/config.js.template \
> /usr/share/nginx/html/config.js
nginx -g 'daemon off;'
// Access in React
const config = (window as any).__ENV__;
const API_URL = config.API_URL || 'http://localhost:3000';
// Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
data:
API_URL: "https://api.production.com"
ENVIRONMENT: "production"
FEATURE_FLAGS: "new-ui,beta-checkout"
Environment Best Practices:
- Local Development: Use .env files with dotenv. Never commit .env, only .env.example
- CI/CD: Store secrets in GitHub Secrets, GitLab CI/CD variables, or CircleCI contexts
- Production: Use platform env vars (Vercel, Netlify) or secrets managers (AWS Secrets Manager)
- Validation: Validate all env vars at startup. Fail fast with clear errors. Use Zod/Joi
- Documentation: Maintain .env.example. Document required vs optional. Include in README
Deployment & Delivery Summary
- Jamstack Platforms: Vercel/Netlify for zero-config. Edge functions for low latency. Preview deploys for testing. Free tier 100GB
- CI/CD Pipeline: GitHub Actions for automation. Cache dependencies (10x faster). Parallel tests. Deploy after tests pass
- Docker: Multi-stage builds (50-100MB). Nginx for serving. Non-root user. Layer caching. Security scanning
- CloudFront + S3: Global CDN. Cache static 1yr. HTML no-cache. CloudFront Functions for headers. $0.085/GB
- Deployment Strategies: Blue-green for instant rollback. Canary for gradual rollout. Feature flags for toggle. A/B for testing
- Config Management: 12-factor app. Separate per environment. Zod validation. Never commit secrets. Runtime injection
Production Checklist: Implement CI/CD automation, enable preview deploys, use canary deployments, validate
environment variables, add health checks, enable
monitoring, and maintain rollback capability.
Target <5min deployment time, 99.9% uptime.