Hydration in Depth

Lesson 09 named the hydration tax. This is the menu of cures — the spectrum from hydrate everything, eagerly to don't hydrate at all: selective & progressive hydration, islands, partial hydration via RSC, and resumability (Qwik). The Lead skill is knowing which lever each pulls.

1The frame — hydration is the bill for SSR

Server rendering gives you fast first paint and SEO (Lesson 09). But the HTML it sends is dead — no event handlers, no state. Hydration is the process of making it alive on the client. The whole field below is a single arms race: make that process cheaper. The strategies are just answers to “how much do we hydrate, and when?” [web.dev]

LeverIdeaStrategy
Hydrate all, nowthe baseline — re-run the whole tree eagerly on loadclassic SSR rehydration
Hydrate laterdefer hydration until idle / interaction / visiblelazy / selective / progressive
Hydrate lessonly the interactive bits; leave static HTML aloneislands · partial hydration (RSC)
Hydrate neverserialize state+listeners into HTML, resume on demandresumability (Qwik)
One-liner

“Hydration is the bill SSR hands you on the client. Every strategy here is one move: hydrate later, hydrate less, or don't hydrate at all.”

2What hydration actually does — and why it's expensive

On load, the framework walks the server HTML and “adopts” it. Mechanically: [web.dev]

  1. Download the JS — the whole component bundle must arrive first.
  2. Re-execute every component — run the app code again to rebuild the virtual tree in memory (the same work the server already did).
  3. Walk the DOM & attach listeners — match the virtual tree to the existing server DOM and wire up every event handler and piece of state.

Three things make it costly, and they compound: it's eager (happens on load whether or not you ever interact), all-at-once (one long task, not interruptible by default), and it runs on the main thread of a possibly-weak device. It also re-does work the server already did — and ships the data twice (once as rendered HTML, once as serialized state for the JS to re-read). That long task is a leading cause of poor INP and the “uncanny valley”: the page looks ready but taps do nothing until hydration finishes. [patterns.dev]

Trade-off — the cruel irony of SSR. SSR exists to make slow devices show content sooner — but those same slow devices pay the most for hydration, because re-executing the whole app is CPU-bound. So naïve “SSR everything + hydrate everything” can give a fast FCP but a worse INP than a small CSR app. That paradox is exactly why the rest of this lesson exists.
One-liner

“Hydration re-runs the whole app on the client just to attach handlers — eager, all-at-once, on the main thread. Fast paint, but the slow phone pays twice. That's the bill we're cutting.”

3The cures — later, less, never

Lazy / selective hydration

hydrate LATER — on idle / visible / interaction

Don't hydrate a component until it's needed — when it scrolls into view, when the browser is idle, or on first interaction. React's streaming does this selectively per <Suspense> boundary and prioritises whatever the user clicks first (Lesson 09 §3).

Progressive hydration

hydrate LATER — top-down, in chunks

Hydrate the tree in prioritised pieces over time instead of one blocking pass — critical/above-the-fold first, the rest as the main thread frees up. Breaks the single long task into many small ones, helping INP.

Islands architecture

hydrate LESS — static sea, interactive islands

Ship the page as mostly static HTML with isolated interactive “islands” (a search box, a carousel) that hydrate independently. Astro/Marko. Most of the page ships zero JS. §3.1.

Partial hydration (RSC)

hydrate LESS — server components never hydrate

React Server Components render server-only and ship 0 JS; only "use client" subtrees hydrate (Lesson 09 §4). The React-ecosystem flavour of “hydrate less.”

Islands — the key mental model

Coined by Jason Miller: think of the page as a sea of static, server-rendered HTML with small islands of interactivity embedded in it. Each island is its own tiny app that hydrates on its own schedule; the static sea never hydrates at all. The framework gives you per-island directives for when: [Jason Miller] [Astro]

// Astro — the directive picks the hydration trigger per island
<Header />                              // static — 0 JS, never hydrates
<SearchBox client:load />            // hydrate immediately (above the fold)
<Carousel client:visible />         // hydrate when scrolled into view
<NewsletterForm client:idle />      // hydrate when the main thread is idle

Islands vs RSC — a likely follow-up. They share the goal (ship less JS) but differ: islands are about where interactivity lives on the page (independent client mini-apps in static HTML, often framework-agnostic); RSC is about where a component renders (server vs client) within one React tree. RSC gives you streaming + server data access; islands give you a hard static/interactive split and easy mixing of frameworks. They're increasingly combined. [patterns.dev]

Trade-off — “hydrate later” can mean “interactive later.” Defer a component's hydration and an early tap on it is dropped or delayed — bad if it's a primary action. Islands add build/architecture complexity and cross-island communication is awkward (they're isolated by design). The skill is matching the trigger to the element: client:load for the search box, client:visible for the footer carousel — never the reverse.
One-liner

“Islands flip the default: the page is static by default, interactive by exception. Each island hydrates on its own trigger — load, idle, or visible — so most of the page ships no JS at all.”

4Resumability — the “hydrate never” bet

All of §3 still hydrates — just less, or later. Resumability (Qwik) is a different paradigm: don't hydrate at all. The server serializes not just the HTML but the app's entire state and the event listeners' locations into the HTML itself. The client boots with almost no JS; when you actually click something, Qwik lazy-loads just that handler and resumes execution exactly where the server left off — no re-running of components to “rediscover” the app. [Qwik]

Hydration (React/Vue/etc.)Resumability (Qwik)
On loaddownload bundle, re-execute tree, attach all listenersattach one global listener; do nothing else
Cost scales withapp size — O(n) in components~constant — O(1), regardless of app size
JS at startupthe whole component bundle~0 — code loads on first interaction

The headline: hydration's startup cost grows with the size of your app; resumability's stays roughly flat. For a huge app on a cheap phone, that's the difference between a janky and an instant TTI. The mechanism is event delegation + serialized state: the listener is already described in the HTML, so there's nothing to “wire up.”

Trade-off — resumability buys instant startup with serialization & ecosystem cost. You must serialize a lot of state into the HTML (bigger document; complex/circular state is hard to serialize), debugging the lazy-loaded execution model is less familiar, and the ecosystem (Qwik) is far smaller than React's. It's a strong bet when startup INP on low-end devices is your bottleneck; for a small app the hydration cost was never the problem, so the trade isn't worth it.
One-liner

“Hydration's startup cost grows with the app; resumability's is flat. Qwik serializes the listeners into the HTML and resumes on click instead of replaying the whole app — O(1) startup, not O(n).”

5The Platform angle — pay the bill where it hurts

The bottleneck is real: image- and content-heavy pages, served globally to low-end devices on slow networks in emerging markets — precisely where hydration's CPU cost bites hardest and tanks INP. So the hydration answer isn't academic; it's a conversion lever. How a Lead reasons about it:

Full loop

Concept: hydration is SSR's client-side bill; I pay it down by hydrating less (islands/RSC) and later (lazy/progressive), and I know resumability removes it entirely. Trade-off: deferring hydration risks “interactive later”; resumability buys O(1) startup but costs serialization and an ecosystem switch. Anchor: “We had fast FCP but poor INP on low-end devices; we moved the listing to mostly-static with hydrated islands for filters/map and lazy-hydrated the below-fold — INP dropped without losing SEO.” Impact: in our markets, startup responsiveness is conversion, so cutting hydration CPU is a business metric, not a vanity one. Invite: “If we were greenfield and INP-bound on cheap hardware, I'd seriously prototype a resumable framework; in our React monorepo, islands + RSC get most of the win without the rewrite.”

6Check yourself — scenario quiz

Pick an answer; instant feedback. Push-back style, like the round.

1. In one sentence: what is hydration, and why does SSR need it?

2. Why is hydration expensive — name the properties that make it hurt.

3. The "cruel irony" of SSR + full hydration on low-end devices is…

4. Map the three "levers" to strategies: hydrate later / hydrate less / hydrate never.

5. Describe the islands architecture mental model.

6. In Astro, you have an above-the-fold search box and a below-the-fold image carousel. Which directives?

scn: minimise startup JS without making the primary action laggy.

7. Interviewer: "Islands and RSC — same thing?" Sharpest distinction?

8. How is resumability fundamentally different from all the hydration strategies?

9. The headline performance claim for resumability vs hydration is about scaling. State it.

10. the listing page: fast FCP but poor INP on low-end devices in emerging markets. Highest-leverage fix in a React stack today?

11. When is reaching for a resumable framework (Qwik) the right call — and how do you frame it as a Lead?

0 / 11 answered

Try this aloud before next session: “We server-render for SEO but INP is poor on cheap phones. Walk me through what hydration is costing us and every lever you'd pull — later, less, never — and when you'd actually reach for resumability.” Time to 90 seconds.
Good follow-up topics:
“Quiz me out loud, harder” “The double-data / serialized-state problem?” “How does React's selective hydration pick?” “Qwik's lazy-loading of handlers — how?” “PPR = islands for the server?” “Hydration mismatch errors — causes?”