Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A12
45 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A12 · Part 3 · Advanced

Performance work

Measure, find the actual cost, fix the structure. Memoisation is the last step, not the first.

4 min read8 sections3 exercises
45 of 57

In this chapter

  • Decide what is slow first
  • Reading the React Profiler
  • Fix the structure before adding a cache
  • Long lists: virtualise
  • The bundle

and 3 more

about 4 min
08On this page
  1. Decide what is slow first
  2. Reading the React Profiler
  3. Fix the structure before adding a cache
  4. Long lists: virtualise
  5. The bundle
  6. What users actually measure
  7. Things that are not React's fault
  8. The order to work in

Decide what is slow first

SymptomUsuallyLook at
Slow first loadBundle size, or a blocking requestNetwork panel, bundle analyser
Typing lagsRe-rendering a large tree per keystrokeReact Profiler
Scrolling stuttersToo many DOM nodes, or layout thrashingPerformance panel
Click feels deadLong task blocking the main threadPerformance panel, INP
Layout jumpsImages without dimensions, late-loading fontsCLS in Lighthouse

These have almost nothing in common. "The app is slow" answered with memo is a guess; each row above has a different fix.

Reading the React Profiler

Record an interaction, then read the flame chart: width is time in that component and its children, grey means it did not re-render. Turn on "record why each component rendered" in the settings — without it you are looking at symptoms.

Reason shownMeans
Props changedCorrect behaviour — unless the prop is a new object with the same contents
Parent renderedA missing memo, or state that should live lower
Context changedAn unmemoised provider value, or one context doing too much
Hooks changedA hook returning a new reference every render

Fix the structure before adding a cache

// ✗ every keystroke re-renders the expensive tree
function Page() {
  const [q, setQ] = useState("");
  return <><input value={q} onChange={...} /><Expensive /></>;
}

// ✓ state moved down — Expensive is now a sibling that never re-renders
function Page() {
  return <><SearchBox /><Expensive /></>;
}

// ✓ or pass it as children — the prop is the same element object each time
function Page({ children }) {
  const [q, setQ] = useState("");
  return <><input value={q} onChange={...} />{children}</>;
}

Both remove the problem rather than caching around it, and neither adds a dependency array anybody has to maintain.

Long lists: virtualise

const rows = useVirtualizer({ count: items.length, estimateSize: () => 48, ... });

Five thousand rows means five thousand DOM nodes, and the browser — not React — is what struggles. Virtualisation renders the twenty that are visible. No amount of memo substitutes for it.

It has real costs: Ctrl-F stops finding off-screen content, and it complicates variable heights and accessibility. Reach for it past a few hundred rows, not before.

The bundle

npx vite-bundle-visualizer          // or @next/bundle-analyzer
  • Split by route first — the biggest win per minute of work.
  • Find the one dependency that is a third of the bundle. There usually is one: a date library, an icon set imported wholesale, a chart package.
  • Lazy-load what most people never open — an editor, a modal, an export dialog.
  • Check for duplicates. Two versions of the same package is common and invisible without the analyser.

What users actually measure

MetricGoodUsually caused by
LCP largest contentful paint< 2.5sA slow server response, or a hero image that is not preloaded
INP interaction to next paint< 200msLong tasks — usually a big re-render or an unyielding loop
CLS cumulative layout shift< 0.1Images without dimensions, fonts swapping, content injected above the fold

INP is where React work shows up. A component that takes 300ms to re-render on click is a failing INP, and a transition often fixes it without making anything faster — it just stops the urgent update queueing behind the slow one.

Things that are not React's fault

  • Layout thrashing. Reading offsetHeight after a write forces a synchronous layout. In a loop, that is a freeze. Batch reads, then writes.
  • Animating left and top. Those trigger layout every frame. transform and opacity run on the compositor.
  • Unoptimised images. Frequently more bytes than the entire JavaScript bundle.
  • A blocking third-party script. Check what is on the critical path before profiling your own code.

The order to work in

  1. Measure on a throttled CPU and connection. Your machine is not the user's.
  2. Find the biggest single cost — usually one component, one dependency, or one request.
  3. Fix the structure: move state down, split the component, virtualise, code-split.
  4. Then memoise, at the exact place the profiler pointed at.
  5. Measure again, and delete the optimisation if it did not help.
Interview answer, one sentence

"I profile before changing anything, because 'slow' means five different things and they have five different fixes. Most React slowness is either a large tree re-rendering on every keystroke — fixed by moving state down rather than by memoising — or too many DOM nodes, which needs virtualisation. Memoisation is the last step and only where the profiler pointed — and where the React Compiler is enabled it inserts most of that memoisation itself, so hand-written useMemo becomes the exception rather than the habit."

03Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 9 testsMove the state downintermediate
  • 5 testsSkip the expensive recompute when it is not neededadvanced
  • 5 testsRender the visible rows onlyadvanced
All 54 for this level →
A12

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A11Previous · A11Advanced component patterns
  2. A12you are here
  3. Next · A13AccessibilitySemantics first, keyboard second, ARIA last — and focus management is the part React makes harder.4 min read

01On this page

8 sections · 0%

  1. 01Decide what is slow first
  2. 02Reading the React Profiler
  3. 03Fix the structure before adding a cache
  4. 04Long lists: virtualise
  5. 05The bundle
  6. 06What users actually measure
  7. 07Things that are not React's fault
  8. 08The order to work in

[] chapters · t top