Library
Learning Patterns · 10 of 12
Learning Patterns
UI/UX Design CRITICAL

The Rendering Spectrum: CSR to Islands

Learning Patterns Lydia Hallie & Addy Osmani
rendering CSR SSR SSG iSSG hydration performance web-vitals TTFB FCP LCP TTI

Key Principle

Every rendering pattern is a trade-off across performance KPIs, not an upgrade over another. The rendering location (server vs. client) and timing (build-time vs. request-time vs. runtime) determine which metrics improve and which degrade. Choosing a pattern means choosing which KPI to prioritize.

Why This Matters

JS bundle size is the single dominant performance lever -- it degrades FCP, LCP, TTI, and TBT simultaneously. The entire rendering spectrum exists to manage where and when that JS executes. Picking the wrong pattern for a use case creates structural performance debt that no amount of optimization can fully resolve.

Good Examples

Pattern-to-KPI Map:

Pattern TTFB FCP/LCP TTI Best Use Case
CSR Low (empty shell) Poor (blocked by JS) Poor (TTI >>> FCP) High-interactivity SPAs where SEO is secondary
Classic SSR High (per-request processing) Good (pre-rendered HTML) Good (FCP = TTI, minimal JS) Static content: news, encyclopedias
SSR + Hydration High Good Degraded (TTI > FCP) Content-first pages needing client interactivity
Streaming SSR Low, consistent Good (chunked flush) TTI > FCP Pages decomposable into independent chunks (search results)
SSG Lowest (pre-built, CDN-cached) Excellent Excellent (FCP ~ TTI) Infrequently changing content (About, Contact, blogs)
iSSG Low (cached static files) Excellent Excellent High-volume content with moderate change frequency (product listings)
Progressive Hydration Varies Good Improved (incremental JS) Interactive pages where some components can defer activation

TTI:FCP Relationship as Decision Heuristic:

  • TTI = FCP: Classic SSR, SSG, iSSG -- page is interactive as soon as it paints.
  • TTI > FCP: SSR with hydration, Streaming SSR, Progressive Hydration -- user sees content before JS finishes attaching handlers.
  • TTI >>> FCP: Full CSR -- empty shell renders instantly but full JS bundle must execute for any interactivity.

Next.js Per-Page Strategy Selection:

  • getStaticProps triggers Static Generation (build-time fetch).
  • getStaticPaths enables SSG for dynamic routes.
  • getServerSideProps triggers SSR (per-request fetch).
  • This allows mixing patterns within one app: marketing pages use SSG, dashboards use SSR or CSR.

Counterpoints

  • CSR with optimizations (code splitting, lazy loading, JS budgeting, preloading, app shell caching) can be sufficient: "sometimes it may be enough to use client-side rendering with some tweaks instead of going for a completely different pattern." The choice depends on whether the bottleneck is initial load (favors SSR/SSG) or interaction responsiveness (favors CSR).
  • SSG breaks down at scale due to three coupled limits: rebuild coupling (any edit forces full rebuild), hosting dependency (performance requires CDN edge-caching), and content staleness (pages frozen at build time).
  • SSR degrades TTFB under load because every request is processed from scratch -- "even if the output of two consecutive requests is not very different, the server will process and generate it from scratch."
  • iSSG uses stale-while-revalidate, meaning users may briefly see outdated content during background regeneration.

Key Quotes (with citations)

  • "The decision on how and where to fetch and render content is key to the performance of an application." (Introduction)
  • "The Chrome team has encouraged developers to consider static rendering or server-side rendering over a full rehydration approach." (Introduction)
  • "As the size of bundle.js increases, the FCP and TTI are pushed forward. This implies that the user will see a blank screen for the entire duration between FP and FCP." (JavaScript bundles and Performance section)
  • "Lesser JavaScript leads to quicker FCP and TTI [...] FCP = TTI. With SSR, users will not be left waiting for all the screen elements to appear and for it to become interactive." (SSR, p. 2)
  • "iSSG uses the stale-while-revalidate strategy where the user receives the cached or stale version while the revalidation takes place. The revalidation takes place completely in the background without the need for a full rebuild." (iSSG, p. 3)
  • "These variations use a combination of techniques to lower one or more of the performance parameters like TTFB (Static and Incremental Static Generation), TTI (Progressive Hydration) and FCP/FP (Streaming)." (Conclusion, p. 2)

Rules of Thumb

  1. Start with static. If content does not change per-user or per-request, SSG or iSSG gives the best KPI profile across the board.
  2. Budget JS at 100-170KB min+gzip. Beyond this threshold, FCP and TTI degrade measurably on CSR pages.
  3. Use the TTI:FCP gap as a diagnostic. A large gap signals excessive hydration cost or bundle size.
  4. SSR and CSR disadvantages are mirror images. SSR: fast initial load, slow interactions. CSR: slow initial load, fast interactions. Pick based on the dominant user journey.
  5. iSSG over SSG when page count is high or content updates moderately. It eliminates full rebuilds via per-page background regeneration (available since Next.js 9.5, granularity as low as 1 second).
  6. Code splitting is non-negotiable for CSR. Route-based splitting should be the default; component-based splitting via dynamic import() handles heavy components.
  7. iSSG failure is graceful. "Even if the regeneration fails in the background, the old version remains unaltered" -- users always see a page.

Related References

  • CSR optimization techniques (JS budgeting, preloading, lazy loading, code splitting, app shell caching)
  • Hydration mechanics: renderToString() on server, hydrate() on client
  • Next.js data-fetching API: getStaticProps, getStaticPaths, getServerSideProps
  • Progressive Hydration and Streaming SSR as TTI/FCP optimizations
  • Web Vitals KPI definitions: TTFB, FCP, LCP, TTI, TBT