Open-Source Wikis

/

Angular

/

Systems

/

Hydration

angular/angular

Hydration

Reusing server-rendered DOM at client boot instead of destroying and re-creating it. Lives in packages/core/src/hydration/ and packages/core/src/render3/hydration/, with cooperating code in @angular/platform-server (emits hydration annotations) and @angular/platform-browser (registers the providers).

Why it exists

Without hydration, an SSR app:

  1. Renders the server HTML (visible immediately).
  2. Boots the client app.
  3. Throws away the server DOM and re-creates it from the same templates.
  4. Re-binds all event listeners.

That sequence causes a visible flash and re-runs all the rendering work. Hydration replaces step 3: the client matches its template execution against the existing DOM and reuses each node.

High-level flow

graph LR
  subgraph Server
    Render["renderApplication()"] --> Annotate["Emit DOM + hydration annotations"]
  end
  Annotate --> HTML["HTML response<br/>(includes <script> with TransferState)"]
  HTML --> Browser
  subgraph Browser
    Browser --> Boot["bootstrapApplication"]
    Boot --> Provide["provideClientHydration()"]
    Provide --> Walk["Hydration walker matches DOM nodes"]
    Walk --> Reuse["Reuse existing DOM"]
    Walk --> Replay["Replay queued events"]
  end

The annotations encode which DOM nodes correspond to which template positions. They survive the transport because they're plain data-* attributes (and a small inline <script> for transfer state).

Annotations

Hydration annotations live as data- attributes on container/anchor elements. The runtime emits them through helpers in packages/core/src/render3/hydration/, and a matching set of helpers reads them at boot. The annotation set has grown over time:

  • Element-level annotations to mark hydration roots.
  • View-container annotations to encode dynamic-view boundaries.
  • i18n annotations to handle text translated at compile time.
  • Defer-block annotations for incremental hydration.

Adding a new template construct generally means adding a new annotation form on both the emit and consume sides.

Walking and matching

When the client app boots with provideClientHydration(), the runtime walks the existing DOM in lockstep with template execution. For each LView, it:

  1. Reads the next expected DOM-node annotation.
  2. Finds the matching real DOM node in the current parent.
  3. Stores the real node in the LView's element slot, instead of calling createElement.
  4. Replays any binding values needed to produce a stable initial state.

If the match fails (the server-rendered DOM doesn't structurally line up with the template), the runtime emits a hydration warning and falls back to client-side rendering for that subtree.

ngSkipHydration

A component that manipulates the DOM directly (e.g., a third-party widget) can mark itself with ngSkipHydration (an attribute or input on the component element). Skipped components are destroyed and re-rendered on the client side, just like the pre-hydration era. The annotation reaches the runtime via packages/core/src/hydration/skip_hydration.ts.

Event replay

Without help, clicks during the hydration window are lost: the user clicks a button before the client app has bound a listener. Angular's hydration setup queues events on the server-rendered DOM and replays them once the matching component has hydrated.

The plumbing is in packages/core/src/event_delegation_utils.ts and the matching event manager in @angular/platform-browser. Server-rendered HTML includes a small inline script that registers a global listener and queues event records keyed by event ID.

Incremental hydration

A @defer block can opt into hydration via @hydrate when ... or @hydrate on .... The server emits the deferred view's HTML alongside an annotation marking it as a hydration boundary; the client doesn't hydrate it on initial boot but waits for the trigger.

When the trigger fires:

  1. Lazy chunks for the deferred view load (same as ordinary defer).
  2. The runtime walks the deferred shell using the hydration annotations.
  3. Matched events that occurred while the shell was unhydrated are replayed.

Logic lives in packages/core/src/defer/incremental_hydration.ts and the surrounding files.

Transfer state

TransferState (packages/core/src/transfer_state.ts) is the JSON island for moving server-side state to the client. The HTTP backend uses it to skip duplicate fetches: a request made on the server caches its response under a synthetic key; the client HttpClient reads from the cache before issuing a new request.

The transfer-state script is emitted by platform-server and parsed by platform-browser during hydration setup.

When hydration fails

Failure modes:

Symptom Likely cause
NG0500 runtime warning about a hydration mismatch Server output doesn't match the client template. Often a difference in conditional rendering due to environment (e.g., if (window) ...).
Visible flash on navigation Deferred view trigger fired before client navigation completed.
Click lost during hydration A component that sets up listeners outside Angular bindings. Mark it ngSkipHydration.
Hydration disabled silently The component or one of its ancestors has ngSkipHydration.

The integration test suite at integration/platform-server-hydration/ is the canonical reproduction harness when fixing hydration bugs.

Where to start when modifying

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Hydration – Angular wiki | Factory