Skip to content

CONCEPT Cited by 2 sources

Progressive hydration

Progressive hydration (sometimes partial hydration when only some subtrees hydrate at all, or selective hydration when priority is dynamic) is the property that a page's interactivity is attached in pieces as each piece becomes ready, rather than in one whole-tree pass. The classic React 17 hydrate() blocked the main thread until every component in the tree was hydrated; React 18's hydrateRoot + <Suspense> breaks that into per-boundary commits that can interleave with user input.

Why it matters

A fully server-rendered page is visible early but isn't interactive until hydration completes. For a large tree, that gap can be seconds — long enough that a user taps a button and nothing happens, because the click handler hasn't attached yet. Progressive hydration moves the above-the-fold, visible-first regions to interactive status before the below-the-fold regions — matching the order the user is likely to engage with them.

Mechanisms

Three distinct stages of "progressive":

  1. Streaming-aligned progressive hydration — each Suspense boundary hydrates as its chunk arrives in the streamed response. This is the React-18 default under renderToPipeableStream + hydrateRoot.
  2. Viewport-driven — hydrate only what's currently in the viewport; delay off-screen regions until they scroll into view. Requires framework support (e.g. Astro partial hydration, some React frameworks with <Suspense> + visibility observers).
  3. Interaction-driven (selective hydration) — when the user clicks on a not-yet-hydrated region, React reprioritises that boundary's hydration ahead of queued ones. React 18 supports this automatically.

Pre-React-18 history

Zalando's Rendering Engine had built partial hydration in-house before React 18 existed: "it also comes with some performance features built inside, including but not limited to streaming, lazy-loading, partial streaming and partial hydration (yes, almost the same concept as in Concurrent React!)." (Source: sources/2023-07-10-zalando-rendering-engine-tales-road-to-concurrent-react.)

Other frameworks that operationalised progressive hydration pre-React-18 include Astro, Marko, Qwik (resumability as a step further), and custom-rolled implementations at several large React sites.

The React-18 arrival makes progressive hydration a built-in capability of the mainline framework rather than a custom-maintained in-house one. Zalando's migration is partly about retiring in-house code whose function is now supported upstream.

Required invariants

  • State injection ordering — the state that drove server rendering of a region must be in the page before that region's markup, or the region hydrates into a mismatch. Confluence uses a NodeJS objectMode transform to enforce this (Source: sources/2026-04-16-atlassian-streaming-ssr-confluence).
  • Independent data resolution per boundary — each boundary's data must be resolvable without knowing any other boundary's resolution state, or progressive hydration degenerates to whole-tree-blocking hydration.
  • HTML chunk-boundary preservation — every intermediate layer (compression, proxy, CDN) must be flush-friendly or the browser sees the page in one big chunk at the end. See concepts/head-of-line-buffering.

Anti-patterns

  • Too many boundaries. Each Suspense boundary has a cost (runtime overhead, per-boundary render on shared-context changes under React 18). See concepts/react-hydration.
  • A boundary per leaf component. The unit of progressive hydration should match the unit of user-visible region (nav, hero, above-fold product card grid, below-fold recs).
  • Boundaries around fast content. Pure overhead; the fast content would hydrate inside its parent boundary for free.

Seen in

Last updated · 501 distilled / 1,218 read