React performance cheatsheet.
Measure first
Use React DevTools Profiler:
- Install React DevTools browser extension.
- Open Profiler tab.
- Hit record → interact → stop.
- 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
- Large unvirtualized lists → virtualize.
- Re-renders cascading → check if children should be memoized.
- Expensive computations in render → useMemo or move outside.
- Context value changes → split context.
- 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 windowbranches- 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 .