FE Lead prep · ref 09 · CSR / SSR / SSG / ISR / streaming / RSC · §2.1 near-certain Q
The frame — ONE axis
WHERE (browser / server / build) & WHEN (build-time / request-time / on-demand) is the first-paint HTML built — & how much JS do we ship to hydrate it? Six acronyms = points on one spectrum. Pick per surface, not per app.
The family (what each optimizes / when)
Strategy
Build where/when
Wins / costs · pick when
CSR
browser, request-time
fast TTFB, slow FCP, bad SEO · app-like, gated, no-SEO dashboards
SSR
server, per request
fast FCP + SEO + fresh; TTFB grows + hydration tax · dynamic + SEO + per-req data
SSG
build machine, build-time
best TTFB/FCP, cheap, CDN; stale till rebuild · content stable between deploys
ISR
build-time + bg regen
static speed + eventual freshness (SWR for pages) · millions of mostly-stable pages
Streaming SSR
server, flush in chunks
TTFB not hostage to slowest query; via <Suspense> · slow part + fast part
RSC
server, 0 JS shipped
less hydration → better INP; needs server runtime · data/library-heavy pages
Metrics they trade
TTFB — static/CDN fast; per-req SSR slower.
FCP/LCP — server HTML wins (content in 1st response).
TTI/INP — ship less JS (RSC/islands).
SEO — any server/static HTML.
Freshness — SSR per-req > ISR windowed > SSG build-only.
Hydration tax (why streaming/RSC exist)
Server sends HTML → browser downloads JS → re-runs tree to attach handlers = redo server work on weak device. Inert till done (uncanny valley) → top cause of bad INP.
Streaming SSR + Suspense
<SearchResults /> // fast → flush now
<Suspense fallback={<Skeleton/>}>
<Reviews /> // slow → stream in when ready
</Suspense>
Classic SSR = all-or-nothing: no HTML till every fetch resolves + can't hydrate till whole bundle loads. Streaming flushes shell now, streams slow subtree later; React selectively hydrates chunks + prioritises what user clicks.
RSC — pay down the JS itself
async function HotelPage({id}){ // Server Component (default) = 0 KB client
const h = await db.hotels.find(id); // DB/heavy libs stay server-side
return <article>{h.name}<BookButton id={id}/></article>;
}
// BookButton.tsx
"use client" // ← only islands ship JS & hydrate
RSC ships 0 JS for itself → less to hydrate → better TTI/INP. Don't make hydration faster — have less of it.
Props crossing server→client boundary must be serializable (no functions / class instances). Define handlers inside the client component.
Platform whiteboard — per surface
Surface
Strategy · why
Marketing / city / SEO content
SSG/ISR — static at edge, ISR refreshes prices w/o full rebuild
Search results listing
Streaming SSR + edge cache — SEO + fast FCP, facets/reviews stream in
Filters / datepicker / map
Client island (CSR) — interactive, no SEO; small "use client"
Hotel detail (millions)
ISR + streaming — static + revalidate; stream live price/availability
+ cross-cutting: edge-cache logged-out HTML · code-split per route · i18n locale routing + hreflang · RUM p75 LCP/INP across markets.
PPR — Partial Prerendering (the synthesis)
Collapses static-vs-dynamic from per-ROUTE → per-COMPONENT, one response. Build-time: prerender static shell (outside <Suspense>). Request-time: shell ships instant from edge; dynamic holes (inside Suspense, read cookies()/headers()/searchParams) stream into the same response. Static TTFB + dynamic freshness, no client waterfall.
Model:islands inverted — dynamic holes in a static page (vs L10 islands = interactive islands in a static client page). Same Suspense boundary = the static/dynamic marker.
Use: mostly-static page w/ a few live/personal bits (hotel page: static shell + live price hole). The clean version of "ISR + streaming".
vs Streaming SSR: streaming SSR renders the whole page per request (even the header; not CDN-cacheable), just delivered in chunks. PPR's shell is build-time static, edge-cached, zero server compute — only the holes hit the server. PPR uses streaming for the holes. Pre-PPR a route was all-static OR all-dynamic; PPR = both.
Caveat: was experimental in Next, Next/React-specific; shell can't read request data; every hole needs a fallback (else CLS); a dynamic API anywhere opts the subtree out of the shell.
Lead one-liners (memorize)
Frame "Not six choices — one axis: where & when the HTML is built. I pick the point per surface that balances TTFB, first paint, interactivity, freshness."
Per-surface "No single right strategy — static for content, streaming-SSR for the SEO listing, a client island for filters, fresh-SSR for checkout."
Streaming "Classic SSR is hostage to its slowest query and hydrates all-or-nothing. Suspense flushes the shell now and streams the slow parts in."
RSC "Streaming makes hydration arrive sooner; RSC makes there be less of it — the cheapest hydration is the one you never do."
PPR "PPR collapses the static-vs-dynamic call from per-route to per-component — a static shell from the edge with dynamic holes streamed into the same response. Islands inverted."