React Performance
When to Use
Performance work and audits: slow pages, request waterfalls, oversized bundles, unnecessary re-renders, long lists, Core Web Vitals (LCP/INP/CLS), Server Component boundaries, lazy loading, prefetching.
Stack
React 19 · Next.js App Router · TypeScript strict
Core Rules
- Parallelize independent I/O.
Promise.all() for anything that doesn't depend on the previous result (async-parallel). Check cheap sync conditions before awaiting.
- Never import from a barrel.
@/components pulls the whole graph; import the module (bundle-barrel-imports). Keep import paths statically analyzable.
- Dynamic-import heavy components — charts, editors, 3D, PDF — via
next/dynamic (bundle-dynamic-imports). Defer analytics until after hydration.
React.cache() for per-request dedup in RSC; never module-level mutable request state (that's a correctness bug, not just perf). Pass the minimum across the server/client boundary.
- Derive during render, never in an effect.
useEffect + setState to compute a value costs an extra render and flashes stale UI (rerender-derived-state-no-effect).
- Memoize only with a named problem.
useMemo/memo on a primitive or a cheap expression is noise. Hoist non-primitive defaults; use functional setState for stable callbacks.
- Never define a component inside a component — it remounts its whole subtree every render.
- Ternary, not
&&, for conditional JSX when the left side is numeric — items.length && <List/> renders a literal 0.
- Long lists:
content-visibility: auto with contain-intrinsic-size, or virtualize past ~100 rendered rows.
startTransition/useDeferredValue to keep input responsive while an expensive tree re-renders.
Patterns
- Waterfall audit — read the request timeline, find sequential awaits with no data dependency, collapse into
Promise.all.
- Bundle audit — check for barrel imports, non-analyzable paths, heavy libs in the main chunk.
- Suspense streaming — stream the shell, suspend the slow region.
- Prefetch on intent — preload on hover/focus for perceived instantaneity.
- Audit output —
file:line — rule-id — one-line fix, severity-ordered.
Examples
examples/good-perf.tsx (virtualizer, lazy, memo, prefetch) · examples/good-performance-patterns.tsx (Promise.all, dynamic import, content-visibility, index maps) · examples/bad-performance.tsx (anti-example).
Reference Index
Load only for the specific task:
| Task |
Load |
| The 70-rule taxonomy with citable IDs across 8 categories |
references/react-performance.md |
| Vercel UI + React perf rules in prose form |
references/vercel-ui-rules.md |
| App Router, RSC, Server Actions, caching, Turbopack |
references/nextjs-patterns.md |
| Component size, state init, import and type optimization |
references/token-optimization.md |
| Promise.all/allSettled, workers, streaming SSR, IO batching |
references/parallelization.md |
Main-thread jank — frame budget, INP/TBT/LoAF, task-splitting, forced reflow, content-visibility, will-change, paint cost |
references/rendering-performance.md |
| Making the wait feel short — skeleton vs spinner vs progress, spinner timing, optimistic UI, streaming-boundary placement, speculation rules |
references/perceived-performance.md |
Constraints
PERF-01 no barrel imports · PERF-02 no numeric && in JSX · PERF-04 no transition: all · IMG-01 images declare dimensions · plus the shared baseline. Measure before optimizing — an unmeasured "optimization" is speculative complexity (P2).
1---2name: react-performance3description: React/Next.js performance — waterfalls, bundle size, RSC, memoization, rendering, virtualization, Core Web Vitals. Use when the complaint is speed or the task is an audit — slow pages, request waterfalls, oversized bundles, unnecessary re-renders, long lists, Core Web Vitals, Server Component boundaries, lazy loading, prefetching.4---56# React Performance78## When to Use9Performance work and audits: slow pages, request waterfalls, oversized bundles, unnecessary re-renders, long lists, Core Web Vitals (LCP/INP/CLS), Server Component boundaries, lazy loading, prefetching.1011## Stack12React 19 · Next.js App Router · TypeScript strict1314## Core Rules151. **Parallelize independent I/O.** `Promise.all()` for anything that doesn't depend on the previous result (`async-parallel`). Check cheap sync conditions *before* awaiting.162. **Never import from a barrel.** `@/components` pulls the whole graph; import the module (`bundle-barrel-imports`). Keep import paths statically analyzable.173. **Dynamic-import heavy components** — charts, editors, 3D, PDF — via `next/dynamic` (`bundle-dynamic-imports`). Defer analytics until after hydration.184. **`React.cache()` for per-request dedup** in RSC; never module-level mutable request state (that's a correctness bug, not just perf). Pass the minimum across the server/client boundary.195. **Derive during render, never in an effect.** `useEffect` + `setState` to compute a value costs an extra render and flashes stale UI (`rerender-derived-state-no-effect`).206. **Memoize only with a named problem.** `useMemo`/`memo` on a primitive or a cheap expression is noise. Hoist non-primitive defaults; use functional `setState` for stable callbacks.217. **Never define a component inside a component** — it remounts its whole subtree every render.228. **Ternary, not `&&`, for conditional JSX** when the left side is numeric — `items.length && <List/>` renders a literal `0`.239. **Long lists:** `content-visibility: auto` with `contain-intrinsic-size`, or virtualize past ~100 rendered rows.2410. **`startTransition`/`useDeferredValue`** to keep input responsive while an expensive tree re-renders.2526## Patterns27- **Waterfall audit** — read the request timeline, find sequential awaits with no data dependency, collapse into `Promise.all`.28- **Bundle audit** — check for barrel imports, non-analyzable paths, heavy libs in the main chunk.29- **Suspense streaming** — stream the shell, suspend the slow region.30- **Prefetch on intent** — preload on hover/focus for perceived instantaneity.31- **Audit output** — `file:line — rule-id — one-line fix`, severity-ordered.3233## Examples34`examples/good-perf.tsx` (virtualizer, lazy, memo, prefetch) · `examples/good-performance-patterns.tsx` (Promise.all, dynamic import, content-visibility, index maps) · `examples/bad-performance.tsx` (anti-example).3536## Reference Index37Load only for the specific task:3839| Task | Load |40|---|---|41| The 70-rule taxonomy with citable IDs across 8 categories | `references/react-performance.md` |42| Vercel UI + React perf rules in prose form | `references/vercel-ui-rules.md` |43| App Router, RSC, Server Actions, caching, Turbopack | `references/nextjs-patterns.md` |44| Component size, state init, import and type optimization | `references/token-optimization.md` |45| Promise.all/allSettled, workers, streaming SSR, IO batching | `references/parallelization.md` |46| Main-thread jank — frame budget, INP/TBT/LoAF, task-splitting, forced reflow, `content-visibility`, `will-change`, paint cost | `references/rendering-performance.md` |47| Making the wait feel short — skeleton vs spinner vs progress, spinner timing, optimistic UI, streaming-boundary placement, speculation rules | `references/perceived-performance.md` |4849## Constraints50`PERF-01` no barrel imports · `PERF-02` no numeric `&&` in JSX · `PERF-04` no `transition: all` · `IMG-01` images declare dimensions · plus the shared baseline. Measure before optimizing — an unmeasured "optimization" is speculative complexity (P2).