React Performance Optimization Techniques

Mar 2, 2026 10 min

React apps start snappy but bloat with features: 100+ components, lists, heavy renders. Poor performance loses users and, Google penalizes slow sites. We need to optimize proactively.

Measure First: Tools Setup

Profile before optimizing.

  • React DevTools Profiler: Record renders, spot offenders.
  • Chrome Lighthouse: Core Web Vitals (LCP/CLS/FID).
  • WhyDidYouRender: Detect unnecessary re-renders.

Target: <100ms render, 60fps

Technique: Memoization Trio

React.memo: Skip pure components.

const ExpensiveChild = React.memo(({ data }: { data: number[] }) => {
  // Heavy computation
  const sum = data.reduce((a, b) => a + b, 0);
  return <div>Sum: {sum}</div>;
});

useCallback: Stable functions.

const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []); // Empty deps for stability

  return <ExpensiveChild onClick={handleClick} />;
};

useMemo: Cache expensive values.

const FilteredList = ({ items }: { items: string[] }) => {
  const filtered = useMemo(() => 
    items.filter(item => item.includes('perf')), 
    [items]
  );
  return <ul>{filtered.map(i => <li key={i}>{i}</li>)}</ul>;
};

Impact: 40-60% render reduction in lists

Technique: List Virtu & Keys + Code Splitting

Proper Keys: key={uniqueId} prevents full re-mounts.

React Window (Virtualization): Render 1000+ items as 20.

import { FixedSizeList as List } from 'react-window';

const VirtualList = ({ items }: { items: string[] }) => (
  <List height={500} itemCount={items.length} itemSize={35}>
    {({ index, style }) => (
      <div style={style}>{items[index]}</div>
    )}
  </List>
);

Code Splitting: Lazy load routes/features.

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

<Suspense fallback={<Spinner />}>
  <LazyDashboard />
</Suspense>

Vite bundles: 150KB gzipped vs 2MB unsplit

Technique: State + Bundle Optimization

Split State: Local > Context > Redux. Use useReducer for complex local.

Analyze Bundle: vite-bundle-visualizer—tree-shake lodash, moment.

npm i -D rollup-plugin-visualizer
# vite.config.ts: plugins: [visualizer()]

Remove: lodash.get → native optional chaining.

Technique: Custom Optimizers

useWhyDidIRender: Dev-only hook.

// Add to components during dev
useWhyDidIRender(Parent, { trackAllPureComponents: true });

Throttle/Resizing:

const useWindowSize = () => {
  const [size, setSize] = useState({ width: 0, height: 0 });
  useEffect(() => {
    const handleResize = throttle(() => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    }, 100);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return size;
};

Checklist:

  • Run Lighthouse >90 score.
  • <StrictMode> in dev.
  • React.Profiler in prod builds.
  • Server-side rendering for LCP.

In PhotoSnap, virtualization + memo = buttery scrolls on 10k images.

Benchmark Your Gains

Before/after: Use performance.mark().

performance.mark('render-start');
ReactDOM.render(<App />, root);
performance.mark('render-end');
console.log(`Render: ${performance.measure('render', 'render-start', 'render-end').duration}ms`);

Action Items

  1. Profile your app today.
  2. Memo 3 components.
  3. Virtualize any list >50 items.

~Sheetal Naik