awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

RSC, per-route strategy, and production observability

Crux How React Server Components reduce the hydration surface, the per-route decision frame for picking SSG/ISR/SSR/streaming, and how to observe render strategies in production.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 20 min

A product manager asks: the marketing homepage, the product pages that update pricing a few times a day, the logged-in account dashboard with a slow order-history query, and the checkout flow — should they all use the same rendering strategy? The answer is no, and making the wrong choice costs either TTFB, freshness, INP, or SEO. The correct answer requires a per-route decision, an understanding of RSC’s role, and production telemetry that catches problems before users do.

RSC and SSR are orthogonal axes. React Server Components control which components ever ship JavaScript to the client. A Server Component runs on the server, renders to HTML (or the RSC wire format), and contributes zero bytes to the client bundle — its code never reaches the browser, so it never hydrates. A Client Component ('use client' at the top of the file) does ship code to the client, is SSR’d to HTML for the initial render (if SSR is enabled), and then hydrates. SSR controls when and where the HTML is generated — at request time on the server (SSR), at build (SSG), or on a schedule with caching (ISR). RSC controls how much of that HTML needs to become interactive. A page that is 90% Server Components ships a tiny client bundle and has minimal hydration cost — even with full SSR. The common confusion is thinking RSC replaced SSR; it did not. A well-architected page uses both: Server Components deliver server-computed HTML with zero client cost, and small 'use client' islands handle the genuinely interactive parts.

RSC vs SSR — two independent axes

SSR axis
When/where HTML is generated. Options: at build (SSG), at request (SSR), on a schedule+cache (ISR), or client-only (CSR). Controls TTFB and freshness.
RSC axis
Which components ship JS. Server Components → zero client bytes, zero hydration. Client Components → ship code, SSR’d then hydrated. Controls bundle size and hydration surface.
Both together (Next.js App Router)
Server Components SSR’d and streamed → fast TTFB, small bundle. ‘use client’ leaves hydrated → only interactive parts pay hydration cost. Streaming Suspense boundaries → fast shell, slow data doesn’t block.

The per-route decision frame. The correct approach is never one strategy for the whole application. Every route has a different freshness requirement, interactivity demand, and data latency profile — and those three dimensions determine the strategy:

  • Marketing homepage — content is identical for everyone, changes on marketing’s schedule (days, not minutes). Strategy: SSG. Served directly from the CDN edge, TTFB is tens of milliseconds, it costs zero per-request server work, and a new build on marketing’s deploy refreshes it.

  • Product pages — same content for every user, but prices update a few times a day and SEO requires fast LCP. Strategy: ISR with a revalidation interval (e.g. 4 hours) plus on-demand revalidation triggered by the price feed webhook. The page is a cached static document for the overwhelming majority of requests — excellent LCP and SEO — and freshness is bounded: no request waits more than one interval behind a price update.

  • Account dashboard — per-user, must be fresh, but has one slow query (order history). Strategy: streaming SSR. Render per request for freshness, but wrap the order-history query in a Suspense boundary. The shell and fast sections stream immediately (~50 ms TTFB); the order-history boundary streams in when its query resolves. The user sees content fast; the slow query does not block it.

  • Checkout — per-user, correctness is paramount, high interactivity. Strategy: SSR per request. Keep it mostly Server Components with small 'use client' islands for the form fields and payment inputs. The hydration surface stays minimal — only the interactive form parts hydrate, which keeps INP low even under heavy main-thread pressure.

Minimising hydration cost across all routes. The universal principle: push 'use client' as far down the component tree as possible. A layout component, a card, a data table — these are almost always Server Components. Only the leaf that actually needs onClick, useState, or browser APIs needs to be a Client Component. When 'use client' is pushed to leaves, the client bundle shrinks, the hydration pass is tiny, and INP stays comfortably under 200 ms.

lesson.inset.insight

ISR on the edge: each CDN POP holds its own cache entry. On-demand revalidation (Next.js revalidatePath / Vercel’s purge API) invalidates the entry globally — but there is a brief window (seconds to low tens of seconds) during which some POPs serve the old version and others serve the new one. For price-sensitive pages, the bounded staleness of ISR is a conscious trade: all POPs stale by at most one interval, with on-demand revalidation as the escape valve. If true consistency across all POPs at the moment of an update is required, full SSR per request is the only option.

Production observability. A render strategy only works if you know when it breaks. Three categories of signals:

  1. TTFB and LCP per route — SSG routes should have TTFB under 50 ms from CDN hits; streaming SSR routes should flush the shell in ~50–100 ms. LCP should be under 2.5 s at p75. These are detectable in field data (CrUX) and synthetic monitoring (Lighthouse CI).

  2. INP per page — the hydration long task is the most common INP regression. If INP at p75 exceeds 200 ms on a page that recently added a Client Component or increased bundle size, the hydration cost grew. Correlate with code changes. Real User Monitoring (RUM) tools segment INP by page.

  3. Hydration mismatch rate — React logs mismatch warnings in development, but in production they are silent unless you instrument them. Wire window.addEventListener('error', ...) to catch React’s error boundary reports, or use a framework integration (Next.js instrumentation.ts, Sentry’s React plugin) that captures hydration errors and sends them to your error tracker. Gate on CI: zero new hydration-mismatch errors per deploy. A mismatch in production means a CLS event and a re-render — they compound when the page has many components.

Which RFC?

Which React DOM server API renders a tree as a stream, flushing the shell first and streaming Suspense boundaries as their data resolves?

Quiz

Why are RSC and SSR described as orthogonal rather than competing?

Pick the best fit

An e-commerce product page: same content for every user, prices update 4–6 times a day, must rank in search and load fast. Pick the rendering strategy.

Design challenge

Design the rendering strategy for an e-commerce site: a marketing homepage, category pages, product pages with daily-changing prices, a logged-in account dashboard, and a checkout flow. Optimise first paint, freshness, and interactivity per route.

  • Marketing homepage: identical for everyone, changes on marketing's schedule.
  • Product pages: same for everyone, price updates a few times a day, must load fast and rank in search.
  • Account dashboard: per-user, must be fresh, has one slow 'order history' query.
  • Checkout: per-user, correctness over everything, interactive.
  • Hydration cost must not block early interactions — INP under 200 ms.
  • No hydration-mismatch errors in production.
Recall before you leave
  1. 01
    A page is server-rendered and paints in 400 ms, but users report that buttons do not respond for another ~2 seconds. Explain the mechanism and name three ways to reduce the dead window.
  2. 02
    RSC, SSR, and streaming are often conflated. Lay out what each one independently controls, and how a single Next.js App Router page uses all three.
  3. 03
    What three categories of production signals should you monitor for a rendering strategy, and what is the CI gate for hydration mismatches?
Recap

RSC and SSR solve different problems on different axes. SSR controls when HTML is generated (build, request, cached interval); RSC controls which components ship JavaScript at all — Server Components contribute zero client bytes and never hydrate. The combination: mostly Server Components keeps the bundle small and hydration cost low, SSR or streaming delivers fast TTFB and LCP, and small 'use client' leaves hydrate quickly. The per-route decision frame: marketing content → SSG; shared content with bounded staleness → ISR with on-demand revalidation; per-user with a slow dependency → streaming SSR with Suspense around the slow query; per-user high-interactivity → SSR with minimal 'use client' surface. Push 'use client' to the smallest leaf that actually needs it. In production: monitor TTFB and LCP per route, INP per page, and hydration-mismatch rate with a CI gate of zero new mismatches per deploy. Hydration is a statefulness question — the server captured a state snapshot that the client must reconstruct identically; every strategy here is a different decision about which state is frozen when and what it costs to carry it across the wire.

Connected lessons
appears again in145
Continue the climb ↑Render strategies: multiple-choice review
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.