Production Build Process
Before deploying your React application, you need to create an optimized production build. This process minifies code, removes development warnings, and optimizes performance.
Creating a Production Build
# For Create React App
npm run build
# For Vite
npm run build
# For Next.js
npm run build
npm start # Start production server
# Build output
build/
├── static/
│ ├── css/
│ │ └── main.abc123.css
│ ├── js/
│ │ ├── main.xyz789.js
│ │ └── vendor.def456.js
│ └── media/
├── index.html
└── asset-manifest.json
Build Optimizations:
- Minification: Code is compressed, whitespace removed
- Tree Shaking: Unused code is eliminated
- Code Splitting: JS split into smaller chunks
- Asset Hashing: Files get unique hashes for cache busting
- Environment Variables: Production values are injected
Environment Variables
Different environments need different configuration. React apps handle environment variables securely:
# .env.development
REACT_APP_API_URL=http://localhost:3000/api
REACT_APP_ENVIRONMENT=development
REACT_APP_ANALYTICS_ID=
# .env.production
REACT_APP_API_URL=https://api.myapp.com
REACT_APP_ENVIRONMENT=production
REACT_APP_ANALYTICS_ID=GA-XXXXX-Y
# Access in code
const apiUrl = process.env.REACT_APP_API_URL;
const isDev = process.env.NODE_ENV === 'development';
if (process.env.REACT_APP_ANALYTICS_ID) {
initAnalytics(process.env.REACT_APP_ANALYTICS_ID);
}
Security Warning: Never commit sensitive keys to version control!
- Create
.env.local for secrets (auto-ignored by CRA)
- Use
REACT_APP_ prefix for public variables only
- Store API keys, tokens on backend, not in React app
- Use CI/CD environment variables for deployment secrets
Deploying to Vercel
Vercel offers the easiest deployment experience for React apps, especially Next.js:
# Install Vercel CLI
npm install -g vercel
# Login
vercel login
# Deploy from project directory
vercel
# Deploy to production
vercel --prod
# vercel.json configuration
{
"buildCommand": "npm run build",
"outputDirectory": "build",
"framework": "create-react-app",
"env": {
"REACT_APP_API_URL": "@api-url"
},
"regions": ["iad1"],
"github": {
"enabled": true,
"autoAlias": true
}
}
Vercel Features:
- Automatic Deployments: Push to Git, auto-deploy
- Preview URLs: Every PR gets a unique preview URL
- Edge Network: CDN with 100+ global locations
- Zero Config: Detects framework automatically
- Serverless Functions: Add API routes easily
Deploying to Netlify
Netlify is another excellent platform for static sites and React apps:
# Install Netlify CLI
npm install -g netlify-cli
# Login
netlify login
# Initialize project
netlify init
# Deploy
netlify deploy
# Deploy to production
netlify deploy --prod
# netlify.toml configuration
[build]
command = "npm run build"
publish = "build"
[build.environment]
NODE_VERSION = "18"
REACT_APP_API_URL = "https://api.myapp.com"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
SPA Routing Fix: The redirect rule /* → /index.html is crucial for client-side routing! Without it, refreshing /about returns 404. This tells the server to always serve index.html, letting React Router handle navigation.
Deploying with Docker
Docker containers provide consistent environments across development and production:
# Dockerfile
FROM node:18-alpine AS build
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci
# Copy source and build
COPY . .
RUN npm run build
# Production stage with nginx
FROM nginx:alpine
# Copy build files
COPY --from=build /app/build /usr/share/nginx/html
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Enable gzip compression
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;
# Caching for static assets
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}
# Build and run Docker container
docker build -t my-react-app .
docker run -p 80:80 my-react-app
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
restart: unless-stopped
CI/CD Pipeline with GitHub Actions
Automate testing and deployment with continuous integration:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
build-and-deploy:
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 -- --coverage --watchAll=false
- name: Build
run: npm run build
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
REACT_APP_ANALYTICS_ID: ${{ secrets.ANALYTICS_ID }}
- 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'
CI/CD Best Practices:
- Run Tests: Prevent broken code from deploying
- Lint Code: Enforce code quality standards
- Security Scan: Check for vulnerabilities (
npm audit)
- Preview Deployments: Deploy PRs to staging URLs
- Rollback Plan: Keep ability to revert to previous version
Performance Optimization for Production
Additional optimizations to implement before deployment:
// 1. Code Splitting with React.lazy
import React, { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
// 2. Image Optimization
import { LazyLoadImage } from 'react-lazy-load-image-component';
function ProductImage({ src, alt }) {
return (
<LazyLoadImage
src={src}
alt={alt}
effect="blur"
width={400}
height={300}
/>
);
}
// 3. Service Worker for Offline Support
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(registration => {
console.log('SW registered:', registration);
})
.catch(error => {
console.log('SW registration failed:', error);
});
});
}
Monitoring and Analytics
// Setup error tracking with Sentry
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.REACT_APP_ENVIRONMENT,
tracesSampleRate: 1.0,
});
// Setup Google Analytics
import ReactGA from 'react-ga4';
ReactGA.initialize(process.env.REACT_APP_GA_ID);
function App() {
const location = useLocation();
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname });
}, [location]);
return <Routes>...</Routes>;
}
// Performance monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
ReactGA.event({
category: 'Web Vitals',
action: name,
value: Math.round(value),
label: id,
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
Exercise 1: Deploy your React app to Vercel:
- Create a production build locally and test it
- Set up environment variables for different environments
- Connect your GitHub repository to Vercel
- Configure automatic deployments on push to main
- Set up preview deployments for pull requests
Exercise 2: Create a complete CI/CD pipeline:
- Set up GitHub Actions workflow
- Add steps for: install, lint, test, build
- Configure deployment to your hosting platform
- Add status badges to your README
- Test the pipeline by pushing changes
Exercise 3: Optimize your production build:
- Implement code splitting for at least 3 routes
- Add lazy loading for images
- Set up performance monitoring (Web Vitals)
- Configure caching headers for static assets
- Run Lighthouse audit and achieve 90+ scores