Back to React
React
medium
mid

How do you optimize a React application end to end?

Categories: initial load (code splitting, RSC, image pipeline, critical CSS), runtime renders (memo + stable refs, split context, virtualize, defer non-urgent updates), data layer (React Query for caching/dedup, optimistic UI), bundle hygiene (analyzer, tree shaking, dynamic imports), observability (web-vitals RUM, Sentry). Profile before optimizing. Structural fixes beat micro-memo.

5 min read·~10 min to think through

Same territory as [[what-are-common-performance-bottlenecks-in-react-apps]] and [[how-do-you-debug-performance-issues-in-react]]. Here's the optimization playbook.

1. Measure first

RUM in production (web-vitals → analytics). Set p75 budgets: LCP < 2.5s, INP < 200ms, CLS < 0.1.

2. Initial load

  • Per-route code splitting (Next.js does this; in Vite/React, React.lazy per route).
  • Tree shaking — verify with bundle analyzer.
  • Replace heavy deps — moment → date-fns, full lodash → per-import or just JS.
  • Dynamic import heavy modals / charts / editors.
  • Image pipeline — AVIF/WebP, responsive srcset, <Image> (Next), lazy below the fold.
  • Critical CSS inlined; rest async.
  • Self-host fonts; font-display: swap + preload.
  • Server Components (App Router) — ship less JS.
  • Streaming SSR — flush above-the-fold first.

3. Runtime renders

  • React.memo on genuinely expensive components with stable prop refs.
  • useCallback/useMemo for refs passed to memo'd children or as effect deps.
  • Split context when value changes often → fan-out cost.
  • Move hot state out of Context to a selector store (Zustand).
  • Virtualize lists > ~200 rows.
  • useDeferredValue / startTransition for non-urgent updates.

4. Data layer

  • React Query / SWR for server state — caching, dedup, background refetch.
  • Optimistic UI for sends.
  • Cancel stale requests with AbortController.
  • Avoid waterfalls — parallel fetch when possible; RSC for server-side coordination.

5. Bundle hygiene

  • Analyzer in CI; alert on regressions.
  • Per-route chunks, vendor chunk for cache stability.
  • modulepreload for module-graph deps.
  • No CommonJS bloat in ESM bundle — check.
  • Drop polyfills for modern browsers.

6. Interaction perf (INP)

  • Break long tasks (chunk + yield, scheduler.yield, requestIdleCallback).
  • Workers for heavy compute.
  • Debounce hot handlers.
  • Avoid layout thrash (batch reads/writes).
  • Reduce hydration cost (RSC).

7. Memory

  • Cleanup every subscription in useEffect return.
  • AbortController for fetches.
  • Bounded caches (LRU).
  • Avoid leaky closures over big data.

8. CSS / DOM

  • contain: layout style paint on isolated subtrees.
  • Composite-only animations (transform/opacity).
  • Reduce DOM node count (virtualize, RSC).
  • Avoid expensive selectors (heavy :has).

9. Observability

  • web-vitals → analytics.
  • Sentry with performance + breadcrumbs.
  • Long Tasks API in dev / staging.
  • Bundle budget regression alerts.

Prioritization

Don't do all of this. Start with:

  1. RUM numbers.
  2. Worst metric (LCP, INP, CLS).
  3. Profile the offending route/interaction.
  4. Apply the targeted fix.
  5. Measure delta.
  6. Move to next.

Interview framing

"Structure by category: initial load (code splitting, RSC, image pipeline, critical CSS, self-hosted fonts), runtime renders (memo on hot paths with stable refs, split context, virtualize, transitions), data layer (React Query for server state, optimistic UI, cancellation), bundle hygiene (analyzer + budgets), INP (break long tasks, Workers, debounce), memory (cleanup, bounded caches). Always measure first — RUM in prod for actual user distribution, profile to find the actual hot path, apply structural fixes before micro-memo. The biggest wins are usually RSC + virtualization + image pipeline + caching, not sprinkling useMemo."

Follow-up questions

  • Walk through optimizing a specific app you worked on.
  • What's the single highest-leverage React perf change?
  • When are RSCs the right answer?

Common mistakes

  • Optimizing without measuring.
  • Micro-memo over structural fixes.
  • Ignoring INP and only optimizing for LCP.

Performance considerations

  • The whole topic.

Edge cases

  • Edge runtime trade-offs.
  • Workers across CSP boundaries.
  • Suspense fallback flickers.

Real-world examples

  • Vercel docs perf series, React Server Components blog, web.dev case studies.

Senior engineer discussion

Seniors lead with measurement, prioritize structural fixes, and bring perf budgets into CI.

Related questions