DeveloperBootstrap

JavaScript

React.js Production Optimization Guide

Master React.js production optimization with performance tuning, bundle optimization, code splitting, SSR, caching strategies, monitoring, and enterprise deployment practices.

What you'll learn

  • Analyse and reduce bundle size with code splitting
  • Implement React.lazy and Suspense for route-level splitting
  • Optimise re-renders with memo, useMemo, and useCallback
  • Configure production builds for maximum performance
  • Implement server-side rendering (SSR) and static generation
  • Set up performance monitoring with Web Vitals
  • Configure aggressive caching strategies

Bundle Optimisation

TypeScript
1// vite.config.ts — production build config
2import { defineConfig } from 'vite';
3import react from '@vitejs/plugin-react';
4
5export default defineConfig({
6  plugins: [react()],
7  build: {
8    rollupOptions: {
9      output: {
10        manualChunks: {
11          vendor: ['react', 'react-dom'],
12          router: ['react-router-dom'],
13          query: ['@tanstack/react-query'],
14        },
15      },
16    },
17    chunkSizeWarningLimit: 500,
18    sourcemap: false,         // disable in production
19    minify: 'terser',
20    terserOptions: {
21      compress: { drop_console: true, drop_debugger: true },
22    },
23  },
24});
Bash
1# Analyse bundle size
2npx vite-bundle-visualizer
3# or for webpack:
4npx webpack-bundle-analyzer stats.json

Rendering Performance

React TSX
1import { memo, useMemo, useCallback } from 'react';
2
3// Prevent unnecessary re-renders
4const ExpensiveList = memo(function ExpensiveList({ items, onSelect }: Props) {
5  return (
6    <ul>
7      {items.map(item => <li key={item.id} onClick={() => onSelect(item.id)}>{item.name}</li>)}
8    </ul>
9  );
10});
11
12function Parent() {
13  // Stable reference — doesn't change on every render
14  const handleSelect = useCallback((id: string) => {
15    // handle selection: id
16  }, []);
17
18  // Only recomputed when rawData changes
19  const processedItems = useMemo(
20    () => rawData.map(d => ({ id: d.id, name: d.name.trim() })),
21    [rawData]
22  );
23
24  return <ExpensiveList items={processedItems} onSelect={handleSelect} />;
25}

Code Splitting

React TSX
1import { lazy, Suspense } from 'react';
2import { Routes, Route } from 'react-router-dom';
3
4// Each route is loaded only when navigated to
5const Dashboard = lazy(() => import('./pages/Dashboard'));
6const Settings  = lazy(() => import('./pages/Settings'));
7const Analytics = lazy(() => import('./pages/Analytics'));
8
9export function AppRoutes() {
10  return (
11    <Suspense fallback={<PageSkeleton />}>
12      <Routes>
13        <Route path="/dashboard"  element={<Dashboard />} />
14        <Route path="/settings"   element={<Settings />} />
15        <Route path="/analytics"  element={<Analytics />} />
16      </Routes>
17    </Suspense>
18  );
19}

SSR & Static Generation

For marketing pages and content-heavy routes, use Next.js App Router server components for zero JS on the client and instant First Contentful Paint.

  • Use React Server Components for data-fetching and static content
  • Mark interactive islands with 'use client'
  • Stream large pages with <Suspense> boundaries
  • Use generateStaticParams for pre-rendering known routes at build time

Caching Strategies

TypeScript
1// TanStack Query — server state caching
2import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
4const queryClient = new QueryClient({
5  defaultOptions: {
6    queries: {
7      staleTime: 5 * 60 * 1000,  // 5 minutes
8      gcTime:   30 * 60 * 1000,  // 30 minutes
9      retry: 2,
10    },
11  },
12});
13
14// Nginx cache headers for static assets
15// Cache-Control: public, max-age=31536000, immutable  (hashed filenames)
16// Cache-Control: no-cache                              (index.html)

Performance Monitoring

TypeScript
1import { onLCP, onINP, onCLS } from 'web-vitals';
2import * as Sentry from '@sentry/react';
3
4function sendToAnalytics(metric: Metric) {
5  Sentry.metrics.distribution(metric.name, metric.value, {
6    tags: { page: window.location.pathname },
7  });
8}
9
10onLCP(sendToAnalytics);   // Largest Contentful Paint  < 2.5s
11onINP(sendToAnalytics);   // Interaction to Next Paint < 200ms
12onCLS(sendToAnalytics);   // Cumulative Layout Shift   < 0.1