Key Principle
Pattern selection, rendering strategy, and performance optimization are not independent decisions. Each layer constrains the others: component structure affects hydration cost, rendering strategy determines which performance patterns apply, and performance budgets may force architectural changes. Work through these three decision trees in order -- pattern, rendering, performance -- then iterate as measurements reveal bottlenecks.
Why This Matters
The book demonstrates that intuition about performance is unreliable. A Movies app case study improved from 64% to ~96% Lighthouse score only through disciplined, sequential, measured optimization (Ch. 30). Some "obvious" improvements -- native lazy loading, react-scroll -- actually caused regressions. The playbook below distills the decision logic scattered across 44 chapters into three actionable sequences.
Pattern Selection Guide
Use this decision tree when choosing a React design pattern for a specific problem:
Do you need shared state across distant components?
- Yes, rarely changing (theme, locale, auth) --> Provider Pattern via Context API (Ch. 6)
- Yes, frequently changing (real-time data) --> External state library; Context causes excessive re-renders
- No --> Continue
Do you need to reuse stateful logic across components?
- Yes --> Custom Hooks (Ch. 14). This replaces HOCs and Render Props in nearly all cases.
- Only if you need to uniformly wrap many components with identical concerns (e.g., analytics, auth guards) --> HOC Pattern remains viable (Ch. 15)
Do you need a multi-part component with implicit shared state?
- Yes (e.g., Menu + Toggle + List + Item) --> Compound Pattern with Context API (Ch. 18)
- No --> Continue
Do you need to separate data logic from presentation?
- Yes --> Apply the Container/Presentational principle via Hooks, not wrapper components (Ch. 8)
Do you need global singletons?
- Use ES modules + Object.freeze instead of Singleton classes (Ch. 4)
Rendering Strategy Decision Tree
Select based on content type and which metrics matter most:
Is the page content fully static and changes only at deploy time?
- Yes --> SSG (Ch. 24). Best TTFB via CDN. Limitations: build time scales with page count.
Is the page mostly static but content updates between deploys?
- Yes --> iSSG with revalidate interval (Ch. 25). Stale-while-revalidate keeps CDN speed with freshness.
Is SEO critical and content is per-request dynamic?
- Yes --> SSR (Ch. 23). FCP includes content. Accept slower TTFB as tradeoff.
Is the page mostly static HTML with a few interactive widgets?
- Yes --> Islands Architecture (Ch. 31). Ship JS only for interactive islands. 83% JS reduction reported vs. full-framework approaches.
Is the app highly interactive (dashboard, editor, real-time)?
- Yes --> CSR (Ch. 22) with mitigations: code splitting, app shell caching, preloading.
Do you need fast FCP from SSR but also rich interactivity?
- Yes --> SSR + Progressive/Selective Hydration (Ch. 26, 29). Use React 18 Suspense boundaries to hydrate components independently and close the uncanny valley.
Do you need reduced TTFB from SSR?
- Yes --> Add Streaming SSR via renderToNodeStream (Ch. 27).
Performance Optimization Sequence
Follow this ordered sequence. Each step builds on the previous. Measure after every change.
Step 1: Audit
- Run Lighthouse and capture baseline scores for TTFB, FCP, LCP, TTI, TBT, CLS.
- Identify the largest bundle contributors using webpack-bundle-analyzer or equivalent.
- Flag third-party libraries with outsized impact (Ch. 30: react-slick replacement saved 78% of its weight).
Step 2: Eliminate Dead Code
- Enable tree shaking by ensuring ES module imports and marking packages with sideEffects: false in package.json (Ch. 40).
- Replace heavy libraries with lighter alternatives (e.g., Font Awesome with @svgr/webpack).
Step 3: Split Bundles
- Route-based splitting first -- natural navigation pauses mask loading latency (Ch. 37).
- Component-level splitting second -- use React.lazy + Suspense for below-fold or conditionally rendered components (Ch. 34).
Step 4: Defer Non-Critical Resources
- Import on Visibility for below-fold components via IntersectionObserver (Ch. 35).
- Import on Interaction for third-party widgets (auth, chat, video) using the facade pattern (Ch. 36).
- Inline critical CSS; defer non-critical stylesheets to unblock FCP.
Step 5: Optimize Resource Hints
- Preconnect for cross-origin resources to establish early connections -- 16.61% LCP improvement in case study (Ch. 30).
- Preload for critical resources discovered late in the loading sequence (Ch. 41).
- Prefetch for anticipated next-page resources during idle time; avoid over-prefetching (Ch. 42).
Step 6: Optimize Rendering
- Apply list virtualization (react-window) for datasets exceeding ~1000 items -- ~100x speedup for 10K rows (Ch. 43).
- Follow the PRPL pattern: Push critical resources, Render initial route, Pre-cache via service workers, Lazy-load remaining routes (Ch. 39).
Step 7: Re-measure and Iterate
- Re-run Lighthouse. Compare against Step 1 baseline.
- Address any regressions. The case study found that some changes hurt metrics unexpectedly.
- Repeat steps 2-6 for remaining bottlenecks.
Key Quotes
- "Patterns are time-tested templates but not silver bullets." (Ch. 44, Conclusions)
- "Some 'optimizations' actually cause regressions." (Ch. 30, Core Web Vitals Case Study, paraphrased from measurement findings)
- "ES modules and Object.freeze achieve the same result without the testing and coupling downsides." (Ch. 4, Singleton Pattern)
- "83% JS reduction vs. Next.js/Nuxt.js" (Ch. 31, Islands Architecture, comparing framework approaches)
Related References
- design-patterns-evolution.md -- Detailed pattern-by-pattern analysis and Hook replacements
- rendering-strategy-spectrum.md -- Full rendering strategy comparison with metric tradeoffs
- performance-patterns-toolkit.md -- Deep dive on each performance technique with implementation details
- core-web-vitals-case-study.md -- Step-by-step walkthrough of the Movies app optimization