Optimizing React Performance

Beyond the Obvious

13 min readPerformance

Most React performance problems are not React problems. They are network, bundle size, or layout problems. Profile first. Optimize second. Guess never.

Profiling Your Application

Use React DevTools Profiler and Lighthouse. Measure real-world performance, not local performance:

// Enable React Profiler
import { Profiler } from "react";

function onRenderCallback(
  id: string,
  phase: "mount" | "update",
  actualDuration: number
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

export default function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <YourApp />
    </Profiler>
  );
}

Code Splitting and Lazy Loading

Split your bundle. Load non-critical code later. Most applications can cut initial bundle size by 50%:

// Dynamic import for page components
import dynamic from "next/dynamic";

const AdminPanel = dynamic(
  () => import("@/components/AdminPanel"),
  { loading: () => <div>Loading...</div> }
);

export default function Dashboard() {
  return (
    <>
      <Header />
      <AdminPanel /> {/* Only loads when needed */}
    </>
  );
}

Memoization Patterns

Use memoization strategically. Avoid premature optimization:

// Memoize expensive computations
import { useMemo } from "react";

function ExpensiveList({ items }: { items: Item[] }) {
  const sortedItems = useMemo(
    () => items.sort((a, b) => b.date - a.date),
    [items] // Only recompute when items changes
  );

  return (
    <ul>
      {sortedItems.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
}

// Memoize callbacks passed to children
export function Parent() {
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []);

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

Optimize Images

Images are usually the bottleneck. Optimize aggressively:

import Image from "next/image";

export function OptimizedImage() {
  return (
    <Image
      src="/image.jpg"
      alt="Description"
      width={800}
      height={600}
      placeholder="blur" // Show blurred placeholder while loading
      quality={75} // Reduce quality for web (75-80 is usually fine)
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
    />
  );
}

Network Is Usually the Bottleneck

JavaScript optimizations usually don't matter if your API calls take 5 seconds. Fix the network first:

  • API response time: Profile your backend. Slow queries? Slow APIs? Fix that first.
  • Bundle size: Check your initial JavaScript. Over 100KB? Code split aggressively.
  • Image optimization: Serve WebP. Resize for device. Use CDN.
  • Caching: Cache API responses. Cache static assets for years.

Key Takeaways

  • Measurement beats intuition in performance optimization
  • Network is almost always the bottleneck, not rendering
  • Image optimization matters more than JavaScript micro-optimizations
  • Lazy loading hidden routes can cut initial bundle by 50%+
  • Small memoization wins compound across the application

Future Improvements

  • Implement automatic performance budgets in CI
  • Set up Lighthouse CI for every deployment
  • Create performance monitoring dashboard
  • Profile and optimize API endpoints
← Back to all articles