React.js Fundamentals
Deploying React Applications
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.localfor 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