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.jsat 232KB - only used for date formattinglodashat 189KB - only using 3 functionschart.jsat 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
- Chrome DevTools - Performance profiling and bottleneck identification
- React DevTools Profiler - Component render analysis
- Lighthouse - Overall performance audit and scoring
- webpack-bundle-analyzer - Bundle size visualization
- Web Vitals Extension - Real-time performance metrics
- Sentry Performance - Production monitoring and alerting
Key Takeaways
Performance optimization is a systematic process:
- Measure first - Don't guess, use profiling tools
- Start with quick wins - Bundle size and images often have the biggest impact
- Profile in production - Development mode is always slower
- Optimize iteratively - Small improvements compound
- 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