DailyDevDiet

logo - dailydevdiet

Learn. Build. Innovate. Elevate your coding skills with dailydevdiet!

Chapter 35: Deployment of React Applications

Deployment of React Applications

Deployment of React applications is a crucial step in making your application accessible to users. This chapter covers various deployment strategies, platforms, and best practices for getting your React application from development to production.

Understanding the Build Process

Before deployment, React applications must be built for production. The build process optimizes your application by:

  • Minifying JavaScript and CSS files
  • Bundling modules together
  • Optimizing images and assets
  • Removing development-only code
  • Creating a production-ready bundle

Building Your React Application

# For applications created with Create React App
npm run build

# Or with yarn
yarn build

This command creates a build folder containing optimized static files ready for deployment.

Build Output Structure

build/
├── static/
│   ├── css/
│   │   └── main.[hash].css
│   ├── js/
│   │   ├── main.[hash].js
│   │   └── [chunk].[hash].js
│   └── media/
│       └── [assets]
├── index.html
├── manifest.json
└── favicon.ico

Environment Variables in Production

Setting Up Environment Variables

// .env.production
REACT_APP_API_URL=https://api.myapp.com
REACT_APP_ANALYTICS_ID=GA-12345-6
REACT_APP_VERSION=1.0.0

// Using in your application
const apiUrl = process.env.REACT_APP_API_URL;
const analyticsId = process.env.REACT_APP_ANALYTICS_ID;

Environment-Specific Configurations

// config/environment.js
const config = {
  development: {
    apiUrl: 'http://localhost:3001',
    debugMode: true,
  },
  production: {
    apiUrl: 'https://api.myapp.com',
    debugMode: false,
  },
  staging: {
    apiUrl: 'https://staging.api.myapp.com',
    debugMode: true,
  }
};

export default config[process.env.NODE_ENV];

Static Hosting Platforms

1. Netlify Deployment

Netlify offers seamless deployment with continuous integration.

Manual Deployment

# Build your application
npm run build

# Install Netlify CLI
npm install -g netlify-cli

# Deploy to Netlify
netlify deploy --prod --dir=build

Continuous Deployment Setup

Create a netlify.toml file:

[build]
  publish = "build"
  command = "npm run build"

[build.environment]
  NODE_VERSION = "18"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

2. Vercel Deployment

Vercel provides excellent React support with zero configuration.

# Install Vercel CLI
npm install -g vercel

# Deploy
vercel --prod

Vercel Configuration

Create a vercel.json file:

{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/static-build",
      "config": {
        "distDir": "build"
      }
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/index.html"
    }
  ],
  "env": {
    "REACT_APP_API_URL": "@api-url"
  }
}

3. GitHub Pages Deployment

For React Router applications, you need special handling for client-side routing.

# Install gh-pages
npm install --save-dev gh-pages

# Add to package.json
{
  "homepage": "https://yourusername.github.io/your-repo-name",
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  }
}

# Deploy
npm run deploy

Handling React Router on GitHub Pages

// public/404.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Single Page Apps for GitHub Pages</title>
    <script type="text/javascript">
      var pathSegmentsToKeep = 1;
      var l = window.location;
      l.replace(
        l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
        l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
        l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
        (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
        l.hash
      );
    </script>
  </head>
  <body>
  </body>
</html>

Cloud Platform Deployment

1. AWS S3 + CloudFront

S3 Bucket Setup

# Create S3 bucket
aws s3 mb s3://your-react-app-bucket

# Enable static website hosting
aws s3 website s3://your-react-app-bucket --index-document index.html --error-document error.html

# Upload build files
aws s3 sync build/ s3://your-react-app-bucket --delete

CloudFront Distribution

// cloudfront-config.json
{
  "CallerReference": "react-app-cf-dist",
  "DefaultRootObject": "index.html",
  "Origins": {
    "Quantity": 1,
    "Items": [
      {
        "Id": "S3-your-react-app-bucket",
        "DomainName": "your-react-app-bucket.s3.amazonaws.com",
        "S3OriginConfig": {
          "OriginAccessIdentity": ""
        }
      }
    ]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3-your-react-app-bucket",
    "ViewerProtocolPolicy": "redirect-to-https",
    "Compress": true
  },
  "CustomErrorResponses": {
    "Quantity": 1,
    "Items": [
      {
        "ErrorCode": 404,
        "ResponsePagePath": "/index.html",
        "ResponseCode": "200"
      }
    ]
  }
}

2. Google Cloud Platform

# Install Google Cloud SDK
gcloud init

# Create App Engine app.yaml
echo "runtime: nodejs18" > app.yaml
echo "handlers:" >> app.yaml
echo "- url: /static" >> app.yaml
echo "  static_dir: build/static" >> app.yaml
echo "- url: /(.*)" >> app.yaml
echo "  static_files: build/index.html" >> app.yaml
echo "  upload: build/index.html" >> app.yaml

# Deploy
gcloud app deploy

Server Deployment (Node.js + Express)

Express Server Setup

// server.js
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 5000;

// Serve static files from React build
app.use(express.static(path.join(__dirname, 'build')));

// API routes (if any)
app.get('/api/health', (req, res) => {
  res.json({ status: 'OK' });
});

// Serve React app for all other routes
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build/index.html'));
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Deployment to Heroku

# Create Procfile
echo "web: node server.js" > Procfile

# Add to package.json
{
  "scripts": {
    "heroku-postbuild": "npm run build"
  },
  "engines": {
    "node": "18.x",
    "npm": "9.x"
  }
}

# Deploy to Heroku
heroku create your-app-name
git push heroku main

Docker Deployment

Multi-stage Dockerfile

# Build stage
FROM node:18-alpine AS build

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy build files
COPY --from=build /app/build /usr/share/nginx/html

# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Nginx Configuration

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Handle client-side routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Optimize static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

Docker Commands

# Build Docker image
docker build -t my-react-app .

# Run container
docker run -p 80:80 my-react-app

# Docker Compose
version: '3.8'
services:
  react-app:
    build: .
    ports:
      - "80:80"
    environment:
      - NODE_ENV=production

CI/CD Pipeline Setup

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy React App

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      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 application
      run: npm run build
      env:
        REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}

    - name: Deploy to Netlify
      uses: nwtgck/actions-netlify@v1.2
      with:
        publish-dir: './build'
        production-branch: main
        github-token: ${{ secrets.GITHUB_TOKEN }}
        deploy-message: "Deploy from GitHub Actions"
      env:
        NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
        NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

Performance Optimization for Production

Bundle Analysis

# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer

# Add script to package.json
{
  "scripts": {
    "analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"
  }
}

# Run analysis
npm run analyze

Code Splitting Implementation

// Lazy loading components
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./components/Dashboard'));
const Profile = lazy(() => import('./components/Profile'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}

Service Worker for Caching

// public/sw.js
const CACHE_NAME = 'react-app-v1';
const urlsToCache = [
  '/',
  '/static/js/bundle.js',
  '/static/css/main.css',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        return response || fetch(event.request);
      })
  );
});

Security Considerations

Content Security Policy

<!-- In public/index.html -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
              script-src 'self' 'unsafe-inline' https://apis.google.com;
              style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
              font-src 'self' https://fonts.gstatic.com;">

Environment Variables Security

// Never expose sensitive data in REACT_APP_ variables
// ❌ Wrong
REACT_APP_SECRET_KEY=your-secret-key

// ✅ Correct - use on server side only
SECRET_KEY=your-secret-key

// ✅ Correct - safe for client
REACT_APP_API_URL=https://api.example.com

Monitoring and Analytics

Error Tracking Setup

// src/utils/errorTracking.js
import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: process.env.REACT_APP_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  integrations: [
    new Sentry.BrowserTracing(),
  ],
  tracesSampleRate: 1.0,
});

export default Sentry;

Performance Monitoring

// src/utils/analytics.js
import ReactGA from 'react-ga4';

export const initGA = () => {
  ReactGA.initialize(process.env.REACT_APP_GA_MEASUREMENT_ID);
};

export const trackPageView = (path) => {
  ReactGA.send({ hitType: "pageview", page: path });
};

export const trackEvent = (action, category, label) => {
  ReactGA.event({
    action,
    category,
    label,
  });
};

Deployment Checklist

Pre-Deployment Checklist

✅ Run all tests and ensure they pass
✅ Check for console errors and warnings
✅ Verify all environment variables are set
✅ Test the production build locally
✅ Ensure all assets are optimized
✅ Check accessibility compliance
✅ Verify SEO meta tags
✅ Test on different devices and browsers
✅ Review security headers
✅ Check for unused dependencies

Post-Deployment Checklist

✅ Verify application loads correctly
✅ Test all major user flows
✅ Check API integrations
✅ Monitor error tracking
✅ Verify analytics tracking
✅ Test contact forms and submissions
✅ Check SSL certificate
✅ Monitor performance metrics
✅ Verify backup systems
✅ Document deployment process

Troubleshooting Common Issues

White Screen of Death

// Add error boundary to catch JavaScript errors
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

Route Not Found (404) Issues

// Ensure server configuration handles client-side routing
// For Apache (.htaccess)
/*
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QR,L]
*/

// For Nginx
/*
location / {
  try_files $uri $uri/ /index.html;
}
*/

Summary

Deploying React applications involves several considerations from build optimization to platform selection. Key takeaways include:

  • Always build for production before deployment
  • Choose the right hosting platform based on your needs
  • Implement proper CI/CD pipelines for automated deployments
  • Consider security implications and implement appropriate measures
  • Monitor your application post-deployment
  • Have a rollback strategy in case issues arise

The deployment strategy you choose should align with your application’s requirements, team expertise, and budget constraints. Static hosting is often sufficient for client-side applications, while server deployment provides more control and flexibility for complex applications.

In the next chapter, we’ll explore scaling React applications to handle increased load and complexity as your user base grows.

Related Articles

Scroll to Top