FE Lead prep · ref 10 · §2.2 · paying down SSR's client-side bill
The frame — hydration is SSR's bill
Server HTML is dead (no listeners). Hydration makes it alive. Every strategy = one move: hydrate LATER · hydrate LESS · hydrate NEVER.
What hydration does
Download the whole JS bundle.
Re-execute every component (rebuild tree in memory — work the server already did).
Walk DOM + attach listeners & state to existing server HTML.
Why it's expensive
Eager — runs on load even if you never interact.
All-at-once — one long non-interruptible task.
Main thread of a weak device.
Data shipped twice (HTML + serialized state).
→ top INP cost · "uncanny valley".
Cruel irony: SSR helps slow devices paint sooner, but slow devices pay most to hydrate (CPU-bound). "SSR + hydrate everything" = fast FCP, can lose INP vs lean CSR.
static HTML sea + isolated interactive islands; most of page ships 0 JS
LESS
partial hydration (RSC)
server components ship 0 JS; only "use client" subtrees hydrate
NEVER
resumability (Qwik)
serialize state+listeners into HTML; resume on click, no replay
Islands — the model + directives
Sea of static server HTML with small islands of interactivity, each hydrating independently on its own trigger. Static by default, interactive by exception (Jason Miller).
<Header /> // static — 0 JS
<SearchBox client:load /> // hydrate now (above fold)
<Carousel client:visible /> // hydrate on scroll-in
<Newsletter client:idle /> // hydrate when main thread idle
Islands vs RSC: same goal (less JS), diff axis. Islands = where interactivity lives on the page (independent mini-apps, framework-agnostic). RSC = where a component renders (server vs "use client") in one React tree + streaming + server data. Increasingly combined.
Resumability — "hydrate never"
Hydration (React/Vue)
Resumability (Qwik)
On load
download + re-execute tree + attach all listeners
attach ~1 global listener; nothing else
Scales with
app size — O(n)
~constant — O(1)
Startup JS
whole bundle
~0 (loads on 1st interaction)
Headline: hydration startup cost grows with app size; resumability stays flat. Big app + cheap phone = where it wins.
Platform angle
Default = hydrate LESS: listing = mostly static content + islands/"use client" for filters/map/favourite; lazy-hydrate below-fold. Highest leverage, works in today's React, no rewrite.
Tune trigger per island:client:load above fold, client:visible below.
Resumability = considered bet, not default: only if RUM shows startup INP on low-end devices dominates & islands maxed → weigh serialization + ecosystem cost.
Lead one-liners (memorize)
Frame "Hydration is the bill SSR hands you on the client. Every strategy is one move: hydrate later, less, or never."
Cost "It re-runs the whole app on the client just to attach handlers — eager, all-at-once, main thread. Fast paint, but the slow phone pays twice."
Islands "Islands flip the default: static by default, interactive by exception — each island hydrates on its own trigger, most of the page ships no JS."
Resume "Hydration's startup cost grows with the app; resumability's is flat — Qwik serializes listeners into the HTML and resumes on click instead of replaying."