React performance cheatsheet.

Measure first

Use React DevTools Profiler:

  1. Install React DevTools browser extension.
  2. Open Profiler tab.
  3. Hit record → interact → stop.
  4. Look at flame graph for slow renders.

Don’t optimize without data.

Profiler API

import { Profiler } from "react";

<Profiler id="UserList" onRender={(id, phase, actualDur, baseDur) => {
  console.log(id, phase, actualDur);
}}>
  <UserList />
</Profiler>

Common bottlenecks

  1. Large unvirtualized lists → virtualize.
  2. Re-renders cascading → check if children should be memoized.
  3. Expensive computations in render → useMemo or move outside.
  4. Context value changes → split context.
  5. Synchronous heavy work → useTransition.

Why-did-you-render

npm i -D @welldone-software/why-did-you-render
// src/wdyr.ts
import React from "react";
import wdyr from "@welldone-software/why-did-you-render";
wdyr(React, { trackAllPureComponents: true });

Logs unnecessary re-renders to console.

Lazy loading

const Settings = React.lazy(() => import("./Settings"));

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

Splits the bundle — only loads when used.

Route-level lazy

const routes = [
  { path: "/", element: <Home /> },
  { path: "/settings", element: <Suspense><Settings /></Suspense> },
];

Suspense for data

function User({ promise }: { promise: Promise<User> }) {
  const user = use(promise);
  return <p>{user.name}</p>;
}

<Suspense fallback={<Spinner />}>
  <User promise={fetchUser(id)} />
</Suspense>

startTransition

const [isPending, startTransition] = useTransition();

function onChange(e) {
  setQuery(e.target.value);     // urgent
  startTransition(() => {
    setResults(filter(big, e.target.value));   // non-urgent
  });
}

return (
  <>
    <input onChange={onChange} />
    {isPending && <Spinner />}
    <List items={results} />
  </>
);

UI stays responsive even with heavy updates.

useDeferredValue

const deferred = useDeferredValue(query);
const results = useMemo(() => filter(big, deferred), [deferred]);

Renders with old deferred while new query is being processed.

List virtualization

See cheatsheet 06. For 100+ items.

Production builds

NODE_ENV=production npm run build

Dev React is much slower. Always profile prod builds.

Bundle size

# Analyze
npx vite-bundle-visualizer       # Vite
npx @next/bundle-analyzer        # Next

# Check before adding deps
npx bundlephobia <pkg>

Avoid unnecessary re-renders

Common culprits:

// Object literal in JSX → child sees new prop each render
<Child config={{ a: 1, b: 2 }} />

// Function in JSX
<Child onClick={() => doIt()} />

// Spreading wide objects
<Child {...everything} />

Most don’t matter unless Child is memoized and the list is large.

React Compiler

// next.config.js
module.exports = {
  experimental: { reactCompiler: true },
};

Auto-memoizes. Treat like a “free pass” on most manual memo work.

Image optimization

// Next.js
<Image src="/a.jpg" width={400} height={300} />

// HTML
<img loading="lazy" decoding="async" srcSet="a.jpg 1x, a-2x.jpg 2x" />

Reduce LCP by loading critical images eagerly and below-fold lazily.

Pre-rendering / SSR / SSG

  • SSG: best for static content. Smallest TTFB.
  • SSR / RSC: dynamic content, decent TTFB, full HTML.
  • CSR: only when nothing else works.

Hydration mismatches

Hydration failed because the initial UI does not match what was rendered on the server.

Common causes:

  • Date.now() / Math.random() in render
  • Locale-dependent formatting
  • typeof window branches
  • Browser extensions modifying DOM

Wrap dynamic-only parts in useEffect + state.

Use key to remount

For state reset:

<EditForm key={user.id} user={user} />

When user.id changes, EditForm fully remounts (state reset).

Common mistakes

  • Optimizing before measuring.
  • Premature React.memo everywhere — overhead exceeds savings.
  • Lazy loading critical above-fold components.
  • Inline data fetching in unmemoized component.
  • Forgetting prod build when profiling.

Read this next

If you want my profiling + virtualization patterns, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .