Staff Engineer150 min read

Performance Optimization at Enterprise Scale

Deep dive into performance optimization strategies, profiling, monitoring, and optimization patterns for large-scale applications.

Topics Covered:

ProfilingBundle OptimizationRuntime PerformanceMonitoring

Prerequisites:

  • Creating Your Own React Library

Overview

At enterprise scale, performance optimization is a systematic discipline requiring measurement, optimization, and continuous monitoring. This tutorial covers the complete performance optimization lifecycle used by companies like Google, Facebook, and Netflix. You'll learn profiling techniques, bundle optimization strategies, runtime performance patterns, monitoring solutions, and how to establish performance budgets and SLAs for large-scale React applications serving millions of users.

Lesson 1: Establishing Performance Metrics and Goals

Before optimizing, establish what you're optimizing for. Different metrics matter at different stages. Core Web Vitals (Google's Metrics): • LCP (Largest Contentful Paint): < 2.5s • FID (First Input Delay): < 100ms • CLS (Cumulative Layout Shift): < 0.1 Performance Metrics: • Time to First Byte (TTFB): < 600ms • First Contentful Paint (FCP): < 1.8s • Time to Interactive (TTI): < 3.8s • Total Blocking Time (TBT): < 200ms • Bundle Size: Track JavaScript bundle sizes Setting Performance Budgets: • Maximum bundle size per route • Maximum number of requests • Performance budgets in CI/CD • Alerting thresholds Real User Monitoring (RUM): • Collect metrics from real users • Identify slow devices/networks • Track p50, p75, p95, p99 percentiles

Code Example:
// Example: Performance budget configuration

// .budgetrc.js or package.json
module.exports = {
  budget: [
    {
      path: '/',
      timings: [
        {
          metric: 'interactive',
          budget: 3000, // 3 seconds
        },
        {
          metric: 'first-meaningful-paint',
          budget: 2000,
        },
      ],
      resourceSizes: [
        {
          resourceType: 'script',
          budget: 200, // 200KB
        },
        {
          resourceType: 'total',
          budget: 500, // 500KB total
        },
      ],
      resourceCounts: [
        {
          resourceType: 'third-party',
          budget: 10, // Max 10 third-party requests
        },
      ],
    },
  ],
};

// Lighthouse CI configuration
// .lighthouserc.js
module.exports = {
  ci: {
    collect: {
      numberOfRuns: 3,
      url: ['http://localhost:3000'],
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
      },
    },
  },
};

// Performance monitoring setup
// utils/performance.ts
export function trackPerformance() {
  // Core Web Vitals
  import('web-vitals').then(({ onCLS, onFID, onLCP }) => {
    onCLS(console.log);
    onFID(console.log);
    onLCP(console.log);
  });
  
  // Custom metrics
  performance.mark('app-start');
  window.addEventListener('load', () => {
    performance.mark('app-loaded');
    performance.measure('app-load-time', 'app-start', 'app-loaded');
    
    const measure = performance.getEntriesByName('app-load-time')[0];
    // Send to analytics
    analytics.track('performance', {
      loadTime: measure.duration,
    });
  });
}

Establish clear performance budgets and metrics. Use Lighthouse CI to enforce budgets in CI/CD. Track Core Web Vitals and custom metrics from real users.

Lesson 2: Profiling with React DevTools and Chrome DevTools

Profiling identifies bottlenecks. Learn to use professional profiling tools effectively. React DevTools Profiler: • Record component renders • Identify expensive renders • Find unnecessary re-renders • Measure render times Chrome DevTools Performance: • Record JavaScript execution • Analyze flame charts • Identify long tasks • Find memory leaks Chrome DevTools Memory: • Heap snapshots • Allocation timeline • Memory leaks detection

Code Example:
// Using React DevTools Profiler

// 1. Install React DevTools browser extension
// 2. Open DevTools → Profiler tab
// 3. Click record
// 4. Interact with your app
// 5. Stop recording
// 6. Analyze results

// What to look for:
// - Components rendering unnecessarily
// - Long render times
// - Components causing cascading re-renders
// - Expensive calculations during render

// Programmatic profiling
// utils/profiler.ts
import { Profiler } from 'react';

interface ProfilerResult {
  id: string;
  phase: 'mount' | 'update';
  actualDuration: number;
  baseDuration: number;
  startTime: number;
  commitTime: number;
}

function onRenderCallback(
  id: string,
  phase: 'mount' | 'update',
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number
) {
  // Log slow renders
  if (actualDuration > 16) { // More than one frame
    console.warn(`Slow render: ${id} took ${actualDuration}ms`);
    
    // Send to monitoring
    analytics.track('slow-render', {
      component: id,
      duration: actualDuration,
      phase,
    });
  }
}

// Wrap expensive components
<Profiler id="Dashboard" onRender={onRenderCallback}>
  <Dashboard />
</Profiler>

// Chrome DevTools Performance Recording
// 1. Open DevTools → Performance tab
// 2. Click record
// 3. Interact with app
// 4. Stop recording
// 5. Analyze flame chart

// What to look for:
// - Long tasks (blocking main thread > 50ms)
// - Layout thrashing (repeated layout recalculations)
// - Paint storms (many repaints)
// - JavaScript execution time

// Performance.mark API for custom timing
performance.mark('component-render-start');
renderComponent();
performance.mark('component-render-end');
performance.measure(
  'component-render',
  'component-render-start',
  'component-render-end'
);

// Get all measures
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
  console.log(`${measure.name}: ${measure.duration}ms`);
});

// Memory profiling
// Take heap snapshot before/after actions
const snapshot1 = performance.memory;
// ... do action
const snapshot2 = performance.memory;
const memoryDiff = snapshot2.usedJSHeapSize - snapshot1.usedJSHeapSize;
console.log(`Memory increased by: ${memoryDiff / 1024 / 1024}MB`);

Profiling is the foundation of optimization. Use React DevTools for component-level profiling and Chrome DevTools for JavaScript/memory profiling. Measure before optimizing.

Lesson 3: Bundle Size Optimization Strategies

Bundle size directly impacts load time. At enterprise scale, every KB matters. Optimization Strategies: • Code splitting (route-based, component-based) • Tree-shaking (remove unused code) • Minification and compression • Dependency analysis • Bundle analysis • Dynamic imports Bundle Analysis Tools: • webpack-bundle-analyzer • source-map-explorer • Bundlephobia (for npm packages) • Lighthouse Common Issues: • Large dependencies • Duplicate dependencies • Unused code • Large images in bundles

Code Example:
// Bundle analysis setup

// Install webpack-bundle-analyzer
// npm install -D webpack-bundle-analyzer

// next.config.js or webpack.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Your config
});

// Run: ANALYZE=true npm run build

// Analyze bundle composition
// utils/bundle-analysis.js
const fs = require('fs');
const { execSync } = require('child_process');

// Find large dependencies
execSync('npx source-map-explorer .next/static/chunks/*.js', {
  stdio: 'inherit',
});

// Code splitting strategies

// 1. Route-based splitting (automatic in Next.js)
// Already handled by framework

// 2. Component-based splitting
import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const CodeEditor = lazy(() => import('./CodeEditor'));

function Dashboard() {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <HeavyChart />
    </Suspense>
  );
}

// 3. Library splitting
// Split large libraries into chunks
const MonacoEditor = lazy(() =>
  import('monaco-editor').then(module => ({
    default: module.editor,
  }))
);

// 4. Dynamic imports with conditions
const Chart = lazy(() => {
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    return import('./SimpleChart');
  }
  return import('./AdvancedChart');
});

// Tree-shaking optimization
// ✅ GOOD: Named imports (tree-shakeable)
import { debounce } from 'lodash-es';
// or
import debounce from 'lodash-es/debounce';

// ❌ BAD: Import entire library
import _ from 'lodash';

// Configure tree-shaking in package.json
{
  "sideEffects": false, // Enable tree-shaking
  // or
  "sideEffects": [
    "*.css",
    "./src/polyfills.js"
  ]
}

// Bundle size budgets in CI
// package.json scripts
{
  "scripts": {
    "build": "next build",
    "analyze": "ANALYZE=true next build",
    "check-size": "bundlesize"
  }
}

// bundlesize.config.js
module.exports = [
  {
    path: '.next/static/chunks/pages/_app-*.js',
    maxSize: '200 KB',
  },
  {
    path: '.next/static/chunks/pages/index-*.js',
    maxSize: '100 KB',
  },
];

// Dependency optimization
// Analyze dependency sizes
// Use Bundlephobia or size-limit

// Replace heavy dependencies
// ❌ moment.js (70KB) → ✅ date-fns (2KB)
// ❌ lodash (70KB) → ✅ lodash-es (tree-shakeable) or individual functions
// ❌ axios (13KB) → ✅ native fetch or ky (4KB)

// Remove unused dependencies
// Use depcheck to find unused deps
// npm install -D depcheck
// npx depcheck

// Optimize images
// Use next/image for automatic optimization
// Or use WebP format
// Compress images before bundling

Bundle size optimization requires systematic analysis. Use bundle analyzers to identify large dependencies, implement code splitting, enable tree-shaking, and set size budgets. Replace heavy dependencies with lighter alternatives.

Lesson 4: Runtime Performance Optimization

Optimize how your application runs, not just how it loads. Key Optimization Areas: • Reduce re-renders • Memoization strategies • Virtual scrolling for large lists • Debouncing/throttling • Web Workers for heavy computations • Efficient state updates • Event handler optimization React Performance Patterns: • React.memo for components • useMemo for expensive calculations • useCallback for stable function references • Code splitting at component level • Lazy loading non-critical components

Code Example:
// Runtime performance optimization patterns

// 1. Reduce unnecessary re-renders
// ❌ BAD: Component re-renders on every parent render
function ExpensiveComponent({ data }) {
  const result = expensiveCalculation(data); // Runs every render
  return <div>{result}</div>;
}

// ✅ GOOD: Memoize expensive calculations
function ExpensiveComponent({ data }) {
  const result = useMemo(
    () => expensiveCalculation(data),
    [data] // Only recalculate when data changes
  );
  return <div>{result}</div>;
}

// 2. Memoize components
// ❌ BAD: Re-renders even with same props
function ListItem({ item, onClick }) {
  return (
    <div onClick={onClick}>
      {item.name}
    </div>
  );
}

// ✅ GOOD: Memo prevents re-render with same props
const ListItem = React.memo(function ListItem({ item, onClick }) {
  return (
    <div onClick={onClick}>
      {item.name}
    </div>
  );
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.item.id === nextProps.item.id &&
         prevProps.item.name === nextProps.item.name;
});

// 3. Stable function references
// ❌ BAD: New function created every render
function Parent({ items }) {
  const handleClick = (id) => {
    // handle click
  };
  
  return items.map(item => (
    <ListItem key={item.id} item={item} onClick={handleClick} />
  ));
}

// ✅ GOOD: Stable reference with useCallback
function Parent({ items }) {
  const handleClick = useCallback((id) => {
    // handle click
  }, []); // Stable reference
  
  return items.map(item => (
    <ListItem key={item.id} item={item} onClick={handleClick} />
  ));
}

// 4. Virtual scrolling for large lists
// Use react-window or react-virtualized
import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// 5. Debounce/throttle expensive operations
import { useMemo } from 'react';
import { debounce } from 'lodash-es';

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  // Debounce search API calls
  const debouncedSearch = useMemo(
    () => debounce(async (searchQuery) => {
      const results = await searchAPI(searchQuery);
      setResults(results);
    }, 300),
    []
  );
  
  useEffect(() => {
    if (query) {
      debouncedSearch(query);
    }
    
    return () => {
      debouncedSearch.cancel();
    };
  }, [query, debouncedSearch]);
  
  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

// 6. Web Workers for heavy computations
// worker.js
self.onmessage = function(e) {
  const { data } = e.data;
  const result = heavyComputation(data);
  self.postMessage({ result });
};

// Component
function Component() {
  const [result, setResult] = useState(null);
  const workerRef = useRef<Worker>();
  
  useEffect(() => {
    workerRef.current = new Worker('/worker.js');
    workerRef.current.onmessage = (e) => {
      setResult(e.data.result);
    };
    
    return () => {
      workerRef.current?.terminate();
    };
  }, []);
  
  const handleCompute = () => {
    workerRef.current?.postMessage({ data: largeDataSet });
  };
}

// 7. Efficient state updates
// ❌ BAD: Multiple state updates
function Component() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(count + 1);
    setCount(count + 1); // Doesn't work as expected
    setCount(count + 1);
  };
}

// ✅ GOOD: Functional updates
function Component() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1); // Works correctly
  };
}

// 8. Batch state updates (React 18+ automatic)
// React 18 automatically batches all updates
function Component() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  
  const handleClick = () => {
    setA(1);
    setB(2);
    // React 18: Single re-render
    // React 17: Two re-renders
  };
}

Runtime performance requires careful optimization of renders, calculations, and updates. Use memoization strategically, implement virtual scrolling for large lists, debounce/throttle expensive operations, and use Web Workers for heavy computations.

Lesson 5: Network and Loading Optimization

Optimize how your application loads resources from the network. Optimization Strategies: • Resource hints (preload, prefetch, preconnect) • HTTP/2 and HTTP/3 • CDN usage • Image optimization • Font optimization • Critical CSS • Service Workers for caching • Compression (gzip, brotli) Loading Strategies: • Preload critical resources • Prefetch likely-needed resources • Lazy load below-the-fold content • Progressive loading

Code Example:
// Network optimization techniques

// 1. Resource hints in Next.js
// app/layout.tsx or pages/_document.tsx
<Head>
  {/* Preconnect to external domains */}
  <link rel="preconnect" href="https://api.example.com" />
  <link rel="dns-prefetch" href="https://api.example.com" />
  
  {/* Preload critical resources */}
  <link
    rel="preload"
    href="/fonts/main-font.woff2"
    as="font"
    type="font/woff2"
    crossOrigin="anonymous"
  />
  
  {/* Prefetch likely-needed resources */}
  <link rel="prefetch" href="/dashboard" />
</Head>

// 2. Image optimization
// Use Next.js Image component
import Image from 'next/image';

<Image
  src="/hero.jpg"
  width={800}
  height={600}
  priority // Preload above-the-fold images
  placeholder="blur" // Show blur while loading
  alt="Hero image"
/>

// 3. Font optimization
// fonts.css
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/font.woff2') format('woff2');
  font-display: swap; // Show fallback immediately
  font-weight: 400;
}

// 4. Critical CSS
// Extract above-the-fold CSS
// Use tools like critical or purgecss
const critical = require('critical');

critical.generate({
  src: 'index.html',
  target: 'styles/critical.css',
  width: 1300,
  height: 900,
});

// 5. Service Worker for caching
// public/sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/scripts/main.js',
];

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

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

// 6. HTTP compression
// next.config.js
module.exports = {
  compress: true, // Enable gzip compression
  // Also enable brotli at server level
};

// 7. CDN configuration
// next.config.js
module.exports = {
  images: {
    domains: ['cdn.example.com'],
    loader: 'custom',
    loaderFile: './image-loader.js',
  },
};

// 8. Streaming SSR
// Next.js 13+ supports streaming by default
// app/layout.tsx
export default async function Layout({ children }) {
  return (
    <html>
      <body>
        <Suspense fallback={<Loading />}>
          {children}
        </Suspense>
      </body>
    </html>
  );
}

// 9. Progressive enhancement
// Load critical content first, enhance later
function App() {
  const [enhanced, setEnhanced] = useState(false);
  
  useEffect(() => {
    // Load enhancements after initial render
    import('./enhancements').then(() => {
      setEnhanced(true);
    });
  }, []);
  
  return enhanced ? <EnhancedApp /> : <BasicApp />;
}

// 10. Request deduplication
// Prevent duplicate requests
const requestCache = new Map();

async function fetchData(url: string) {
  if (requestCache.has(url)) {
    return requestCache.get(url);
  }
  
  const promise = fetch(url).then(res => res.json());
  requestCache.set(url, promise);
  
  return promise;
}

Network optimization reduces load times through resource hints, image optimization, font optimization, caching strategies, and compression. Use CDNs and Service Workers for efficient resource delivery.

Lesson 6: Monitoring and Alerting

Performance monitoring ensures you catch regressions before users do. Monitoring Solutions: • Real User Monitoring (RUM) • Synthetic monitoring • Performance budgets • Error tracking • Custom metrics Tools: • Google Analytics (Core Web Vitals) • New Relic • Datadog • Sentry (errors + performance) • Custom dashboards Alerting: • Performance budget violations • Error rate spikes • Slow API responses • Bundle size increases

Code Example:
// Performance monitoring setup

// 1. Core Web Vitals tracking
// utils/analytics.ts
import { onCLS, onFID, onLCP, onTTFB, onINP } from 'web-vitals';

function sendToAnalytics(metric: any) {
  // Send to your analytics service
  analytics.track('web-vital', {
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // 'good', 'needs-improvement', 'poor'
    id: metric.id,
    delta: metric.delta,
  });
}

onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
onINP(sendToAnalytics);

// 2. Custom performance metrics
// utils/performance-monitor.ts
export class PerformanceMonitor {
  private metrics: Map<string, number[]> = new Map();
  
  mark(name: string) {
    performance.mark(name);
  }
  
  measure(name: string, startMark: string, endMark: string) {
    performance.measure(name, startMark, endMark);
    const measure = performance.getEntriesByName(name)[0];
    
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)?.push(measure.duration);
    
    // Send to monitoring service
    this.sendMetric(name, measure.duration);
  }
  
  private sendMetric(name: string, value: number) {
    // Send to monitoring service (DataDog, New Relic, etc.)
    if (window.monitoring) {
      window.monitoring.track(name, value);
    }
  }
  
  getMetrics() {
    const result: Record<string, any> = {};
    this.metrics.forEach((values, name) => {
      result[name] = {
        avg: values.reduce((a, b) => a + b, 0) / values.length,
        p50: this.percentile(values, 50),
        p95: this.percentile(values, 95),
        p99: this.percentile(values, 99),
      };
    });
    return result;
  }
  
  private percentile(arr: number[], p: number) {
    const sorted = arr.sort((a, b) => a - b);
    const index = Math.ceil((sorted.length * p) / 100) - 1;
    return sorted[index];
  }
}

// 3. Performance budget monitoring
// utils/budget-monitor.ts
export function checkPerformanceBudget() {
  // Check bundle sizes
  const scripts = Array.from(document.querySelectorAll('script'));
  const totalSize = scripts.reduce((total, script) => {
    return total + (script.src ? getScriptSize(script.src) : 0);
  }, 0);
  
  const budget = 500 * 1024; // 500KB
  if (totalSize > budget) {
    alertMonitoring({
      type: 'budget-exceeded',
      metric: 'bundle-size',
      value: totalSize,
      budget,
    });
  }
  
  // Check load time
  window.addEventListener('load', () => {
    const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
    if (loadTime > 3000) { // 3 seconds
      alertMonitoring({
        type: 'budget-exceeded',
        metric: 'load-time',
        value: loadTime,
        budget: 3000,
      });
    }
  });
}

// 4. Error tracking with performance context
// utils/error-monitor.ts
window.addEventListener('error', (event) => {
  const performanceData = {
    loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
    domContentLoaded: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
    firstPaint: performance.getEntriesByType('paint')[0]?.startTime,
  };
  
  errorTracking.captureException(event.error, {
    contexts: {
      performance: performanceData,
    },
  });
});

// 5. API performance monitoring
// utils/api-monitor.ts
const originalFetch = window.fetch;

window.fetch = async function(...args) {
  const startTime = performance.now();
  const url = args[0];
  
  try {
    const response = await originalFetch(...args);
    const duration = performance.now() - startTime;
    
    // Track slow API calls
    if (duration > 1000) {
      monitoring.track('slow-api', {
        url,
        duration,
        status: response.status,
      });
    }
    
    return response;
  } catch (error) {
    const duration = performance.now() - startTime;
    monitoring.track('api-error', {
      url,
      duration,
      error: error.message,
    });
    throw error;
  }
};

// 6. React component render monitoring
// HOC for automatic monitoring
function withPerformanceMonitoring<P extends object>(
  Component: React.ComponentType<P>,
  componentName: string
) {
  return function MonitoredComponent(props: P) {
    return (
      <Profiler
        id={componentName}
        onRender={(id, phase, actualDuration) => {
          if (actualDuration > 16) { // More than one frame
            monitoring.track('slow-render', {
              component: id,
              phase,
              duration: actualDuration,
            });
          }
        }}
      >
        <Component {...props} />
      </Profiler>
    );
  };
}

// Usage
export default withPerformanceMonitoring(ExpensiveComponent, 'ExpensiveComponent');

Continuous monitoring catches performance regressions early. Track Core Web Vitals, custom metrics, performance budgets, and errors. Set up alerting for violations to maintain performance standards.

Lesson 7: Performance Testing in CI/CD

Automate performance testing to prevent regressions. CI/CD Performance Testing: • Lighthouse CI integration • Bundle size checks • Performance budget enforcement • Visual regression testing • Load testing Tools: • Lighthouse CI • bundlesize • Performance budgets • WebPageTest API

Code Example:
// CI/CD performance testing setup

// .github/workflows/performance.yml
name: Performance Tests

on: [pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: npm install
      - run: npm run build
      - run: npm run start &
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v7
        with:
          urls: |
            http://localhost:3000
            http://localhost:3000/dashboard
          uploadArtifacts: true
          temporaryPublicStorage: true

// bundlesize check
// package.json
{
  "scripts": {
    "test:size": "bundlesize"
  }
}

// bundlesize.config.js
module.exports = [
  {
    path: '.next/static/chunks/main-*.js',
    maxSize: '200 KB',
  },
  {
    path: '.next/static/chunks/pages/_app-*.js',
    maxSize: '100 KB',
  },
];

// Lighthouse CI config
// .lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
      },
    },
  },
};

// Performance regression testing
// scripts/performance-test.js
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

async function runLighthouse(url) {
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
  const options = { logLevel: 'info', output: 'json', port: chrome.port };
  const runnerResult = await lighthouse(url, options);
  
  await chrome.kill();
  
  const { lhr } = runnerResult;
  const score = lhr.categories.performance.score * 100;
  
  if (score < 90) {
    console.error(`Performance score ${score} below threshold of 90`);
    process.exit(1);
  }
  
  return score;
}

// Run in CI
runLighthouse('http://localhost:3000');

Automate performance testing in CI/CD to catch regressions before they reach production. Use Lighthouse CI for performance scores and bundlesize for bundle size checks.

Lesson 8: Performance Optimization Checklist

Use this comprehensive checklist for performance optimization. Initial Load: • [ ] Code splitting implemented • [ ] Images optimized and lazy loaded • [ ] Fonts optimized with font-display • [ ] Critical CSS extracted • [ ] Bundle size under budget • [ ] Gzip/Brotli compression enabled • [ ] CDN configured Runtime: • [ ] Unnecessary re-renders eliminated • [ ] Expensive calculations memoized • [ ] Virtual scrolling for long lists • [ ] Debounce/throttle expensive operations • [ ] Web Workers for heavy computations Monitoring: • [ ] Core Web Vitals tracked • [ ] Performance budgets configured • [ ] Error tracking with performance context • [ ] Alerting set up • [ ] Dashboard for metrics Testing: • [ ] Lighthouse CI integrated • [ ] Bundle size checks in CI • [ ] Performance regression tests

Code Example:
// Performance optimization checklist implementation

// scripts/performance-check.js
const fs = require('fs');
const { execSync } = require('child_process');

class PerformanceChecklist {
  async runChecks() {
    const checks = {
      bundleSize: await this.checkBundleSize(),
      lighthouse: await this.checkLighthouse(),
      dependencies: await this.checkDependencies(),
      images: await this.checkImages(),
    };
    
    const allPassed = Object.values(checks).every(check => check.passed);
    
    if (!allPassed) {
      console.error('Performance checks failed');
      console.log(checks);
      process.exit(1);
    }
    
    console.log('All performance checks passed!');
  }
  
  async checkBundleSize() {
    const bundlePath = '.next/static/chunks/main-*.js';
    const maxSize = 200 * 1024; // 200KB
    
    // Check bundle size
    // Implementation here
    
    return { passed: true, message: 'Bundle size within budget' };
  }
  
  async checkLighthouse() {
    // Run Lighthouse and check scores
    return { passed: true, message: 'Lighthouse scores passed' };
  }
  
  async checkDependencies() {
    // Check for large dependencies
    return { passed: true, message: 'No oversized dependencies' };
  }
  
  async checkImages() {
    // Check image optimization
    return { passed: true, message: 'Images optimized' };
  }
}

// Run checklist
new PerformanceChecklist().runChecks();

Use a comprehensive checklist to ensure all performance optimizations are implemented. Automate checks where possible.

Conclusion

Performance is a feature, not an afterthought. At enterprise scale, systematic measurement, optimization, and monitoring are essential. Establish performance budgets, profile regularly, optimize continuously, and monitor in production. Remember: optimize based on real user data, not synthetic benchmarks. Performance optimization is an ongoing process, not a one-time task.