| name | web-build-deploy |
| description | Building and deploying React web applications. Use when configuring builds, deploying to Vercel/Netlify, setting up CI/CD, Docker, or managing environments. |
Web Build & Deploy (React)
Vercel Deployment
Quick Deploy
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
# Deploy to production
vercel --prod
Configuration (vercel.json)
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "vite",
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" }
]
}
]
}
Environment Variables
# Add via CLI
vercel env add NEXT_PUBLIC_API_URL
# Or in Vercel dashboard: Settings > Environment Variables
# Access in code
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Preview Deployments
Every push to a branch creates a preview URL automatically:
https://project-git-branch-username.vercel.app
Netlify Deployment
Quick Deploy
# Install Netlify CLI
npm i -g netlify-cli
# Login
netlify login
# Deploy preview
netlify deploy
# Deploy to production
netlify deploy --prod
Configuration (netlify.toml)
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
[build.environment]
NODE_VERSION = "18"
Environment Variables
# Add via CLI
netlify env:set API_URL https://api.example.com
# Or in Netlify dashboard: Site settings > Environment variables
Docker Deployment
Dockerfile (Multi-stage build)
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx config for SPA routing
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf (SPA routing)
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
}
Docker Compose
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
restart: unless-stopped
# With backend
api:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
volumes:
postgres_data:
Build & Run
# Build image
docker build -t myapp:latest .
# Run container
docker run -p 80:80 myapp:latest
# With docker-compose
docker-compose up -d
docker-compose down
GitHub Actions CI/CD
Basic Workflow
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
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:
VITE_API_URL: ${{ secrets.API_URL }}
- name: Deploy to Vercel
if: github.ref == 'refs/heads/main'
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'
With Preview Deployments
name: Preview
on: [pull_request]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy Preview
uses: amondnet/vercel-action@v25
id: deploy
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
- 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: ${{ steps.deploy.outputs.preview-url }}'
})
Environment Configuration
Vite
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
'process.env.API_URL': JSON.stringify(env.VITE_API_URL),
},
};
});
# .env.development
VITE_API_URL=http://localhost:8000
# .env.production
VITE_API_URL=https://api.example.com
Next.js
# .env.local (not committed)
DATABASE_URL=postgresql://localhost:5432/dev
# .env.production
NEXT_PUBLIC_API_URL=https://api.example.com
// Access in code
// Client-side (must start with NEXT_PUBLIC_)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
// Server-side only
const dbUrl = process.env.DATABASE_URL;
Build Optimization
Vite Build Analysis
# Install analyzer
npm i -D rollup-plugin-visualizer
# Add to vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
filename: 'stats.html',
open: true,
}),
],
});
# Build and analyze
npm run build
Code Splitting
// Route-based splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
// Component splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));
Caching Strategy
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
});
Health Checks & Monitoring
Health Check Endpoint
// For Docker/Kubernetes
// api/health.ts (Next.js)
export async function GET() {
return Response.json({ status: 'healthy', timestamp: Date.now() });
}
Error Tracking (Sentry)
// sentry.client.config.ts
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
});
// Wrap app
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
Common Issues
| Issue | Solution |
|---|---|
| 404 on refresh (SPA) | Configure server for SPA fallback |
| Environment vars undefined | Check prefix (VITE_, NEXT_PUBLIC_) |
| Build fails on CI | Check Node version, clear cache |
| Docker image too large | Use multi-stage build, alpine base |
| Slow builds | Enable caching, parallelize |
Deployment Checklist
Before deploying to production:
- Environment variables set
- Build succeeds locally
- Tests pass
- Security headers configured
- Error tracking enabled
- Performance optimized (bundle size, code splitting)
- SEO meta tags (if applicable)
- SSL/HTTPS enabled
- Custom domain configured
- Health check endpoint working