Back to Blog

React Performance Deep Dive: Optimizing Load Time from 3s to 300ms

10 min read
ReactPerformanceWeb VitalsOptimizationFrontend

My React app was slow. Really slow. Initial page load took 3 seconds, interactions were janky, and the Lighthouse score was a depressing 42. Users were bouncing before the page even loaded.

This is the story of how I systematically improved performance, achieving a 90% reduction in load time and reaching a Lighthouse score of 98.

The Problem: Measuring Before Optimizing

First rule of optimization: measure, don't guess. I set up proper performance monitoring to track exactly where time was being spent.

Initial Metrics

Before optimization:

  • First Contentful Paint: 2.8s (target: under 1.8s)
  • Largest Contentful Paint: 4.2s (target: under 2.5s)
  • Time to Interactive: 5.1s (target: under 3.8s)
  • Total Blocking Time: 890ms (target: under 200ms)
  • Bundle Size: 1.2MB (target: under 500KB)
  • Lighthouse Score: 42

Problem 1: Massive Bundle Size

Issue: 1.2MB JavaScript Bundle

After running bundle analysis, I discovered the culprits:

  • moment.js at 232KB - only used for date formatting
  • lodash at 189KB - only using 3 functions
  • chart.js at 148KB - imported but barely used
  • Unused dependencies from old features

Solution: Replace Heavy Dependencies

I replaced moment.js with the native Intl API (0KB overhead) or date-fns (13KB with tree-shaking). For lodash, I switched to individual imports or native JavaScript methods.

Result: Bundle size reduced from 1.2MB to 420KB (65% reduction)

Problem 2: Unnecessary Re-renders

Issue: Component Re-rendering 400+ Times

Using React DevTools Profiler, I found components re-rendering excessively on every state change. The ProductList component was filtering data on every render without memoization.

Solution: useMemo + React.memo

I wrapped expensive computations in useMemo and memoized child components with React.memo. I also split large context objects into smaller, focused contexts so components only re-render when their specific data changes.

Result: Re-renders reduced by 85%, interaction time improved by 60%

Problem 3: Rendering 10,000+ Items

Issue: List with 10K+ Items Blocking Main Thread

Rendering thousands of DOM nodes at once was causing significant performance degradation. The browser struggled to handle that many elements simultaneously.

Solution: Virtual Scrolling with react-window

I implemented virtual scrolling using react-window, which only renders items currently visible in the viewport. This reduced DOM nodes from 10,000 to about 20 at any given time.

For lists with variable item heights, I used VariableSizeList instead of FixedSizeList.

Result: Smooth 60fps scrolling, Total Blocking Time reduced from 890ms to 120ms

Problem 4: Images Killing Load Time

Issue: Unoptimized 5MB Images

Large, unoptimized images were the single biggest contributor to slow Largest Contentful Paint times.

Solution: Next.js Image Optimization

I replaced all img tags with Next.js Image component, which provides automatic optimization, lazy loading, and modern format conversion (WebP/AVIF).

For above-the-fold images, I added the priority prop to load them immediately. For others, I enabled lazy loading with blur placeholders.

Result: Image payload reduced by 78%, LCP improved from 4.2s to 1.8s

Problem 5: Blocking API Calls

Issue: Sequential Waterfall Requests

API calls were chained sequentially - fetch user, then fetch posts, then fetch comments. Each request waited for the previous one to complete.

Solution: Parallel Requests + React Suspense

I restructured data fetching to load resources in parallel using React Suspense boundaries. This allows different parts of the page to load independently without blocking each other.

I also implemented request deduplication to prevent multiple identical API calls when components mount simultaneously.

Result: API waterfall eliminated, Time to Interactive reduced from 5.1s to 2.3s

Problem 6: Expensive State Updates

Issue: Heavy Computation on Every Render

Complex calculations were running on every render, blocking the main thread and making the UI feel sluggish.

Solution: Web Workers for Heavy Computation

I moved expensive data processing operations to Web Workers, which run on separate threads. This keeps the main thread free for UI updates and user interactions.

The worker handles filtering, mapping, and sorting large datasets, then posts the results back to the main thread.

Result: UI remains responsive during heavy calculations

Final Results

After implementing all optimizations:

Performance Improvements:

  • First Contentful Paint: 2.8s → 0.9s (68% faster)
  • Largest Contentful Paint: 4.2s → 1.8s (57% faster)
  • Time to Interactive: 5.1s → 2.3s (55% faster)
  • Total Blocking Time: 890ms → 120ms (87% reduction)
  • Bundle Size: 1.2MB → 420KB (65% smaller)
  • Lighthouse Score: 42 → 98

Performance Checklist

My go-to optimization checklist:

  • Bundle Analysis - Remove unused dependencies
  • Code Splitting - Lazy load routes and heavy components
  • Memoization - Use useMemo, useCallback, React.memo
  • Virtual Scrolling - For lists with 100+ items
  • Image Optimization - Use Next.js Image, WebP/AVIF formats
  • API Optimization - Parallel requests, deduplication
  • Web Workers - Offload heavy computation
  • Context Splitting - Avoid unnecessary re-renders
  • Monitoring - Set up Core Web Vitals tracking

Tools I Used

  1. Chrome DevTools - Performance profiling and bottleneck identification
  2. React DevTools Profiler - Component render analysis
  3. Lighthouse - Overall performance audit and scoring
  4. webpack-bundle-analyzer - Bundle size visualization
  5. Web Vitals Extension - Real-time performance metrics
  6. Sentry Performance - Production monitoring and alerting

Key Takeaways

Performance optimization is a systematic process:

  1. Measure first - Don't guess, use profiling tools
  2. Start with quick wins - Bundle size and images often have the biggest impact
  3. Profile in production - Development mode is always slower
  4. Optimize iteratively - Small improvements compound
  5. Monitor continuously - Performance degrades over time without vigilance

Performance optimization is a journey, not a destination. Set up monitoring, establish baselines, and optimize iteratively. Your users (and your conversion rates) will thank you.

Want to dive deeper? Check out my GitHub for working examples of each optimization technique.

Tech Stack: React 18, Next.js 14, TypeScript, React Query, Web Workers