Open-Source Wikis

/

Vue.js

/

Features

/

Hydration

vuejs/core

Hydration

Hydration is the process of binding existing server-rendered DOM to a client-side component tree without throwing the DOM away and re-creating it. The implementation is one of the most subtle parts of Vue's runtime: packages/runtime-core/src/hydration.ts is ~30KB and almost entirely defensive code that handles every shape of mismatch.

For the SSR side that produces the markup, see server-side rendering.

Entry points

graph TD
    A["createSSRApp(...)"]
    B["createHydrationRenderer(rendererOptions)"]
    C["app.mount(container)"]
    D["hydrate(rootVNode, container)"]
    E["hydrateNode(node, vnode, parent, …)"]
    A --> B --> C --> D --> E

createSSRApp is in packages/runtime-dom/src/index.ts. It calls ensureHydrationRenderer() (which lazily creates a hydration-capable renderer via createHydrationRenderer from runtime-core/src/renderer.ts). On mount, the hydration renderer's hydrate function (defined in hydration.ts) walks the existing DOM and the new vnode tree in lockstep.

hydrateNode

The core function in hydration.ts is hydrateNode(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized). It:

  1. Inspects the existing DOM node's nodeType and nodeName.
  2. Branches on the vnode's ShapeFlags:
    • ELEMENT → match tag, hydrate attributes (events attached, no DOM changes), recurse into children with hydrateChildren.
    • TEXT / COMMENT → match content; warn on mismatch.
    • STATIC → consume one or more sibling nodes equal to the static vnode's count.
    • FRAGMENT → consume between fragment markers (<!---->...<!---->).
    • STATEFUL_COMPONENT / FUNCTIONAL_COMPONENT → set up the component, recurse into its sub-tree.
    • TELEPORT → use the special Teleport.hydrate that knows how to find content at the target selector.
    • SUSPENSE → set up a SuspenseBoundary and decide whether to hydrate the default slot or the fallback based on the data-server-rendered payload.
  3. Returns the next sibling for the caller to continue from.

When a mismatch is detected (text doesn't match, tag doesn't match, attribute set differs):

  • A dev warning is emitted via logMismatchError. The warning includes the vnode location (file/line in compiled SFCs).
  • The server-rendered subtree is removed and replaced by a freshly mounted client vnode tree (full client mount fallback).

Patch flags during hydration

The optimization story carries through to hydration: the same PatchFlags the compiler emitted are honored. With PatchFlags.HYDRATE_EVENTS, the renderer attaches event listeners but skips re-running prop diffing. With STABLE_FRAGMENT, it knows children won't reorder. With BAIL, it disables optimizations and treats the subtree as if it might have any kind of change — used when the runtime can't trust the patch flag (e.g., manual cloneVNode).

Lazy hydration strategies

packages/runtime-core/src/hydrationStrategies.ts exports four strategies for defineAsyncComponent({ hydrate: hydrateOnXxx() }):

  • hydrateOnIdle(timeout?) — wait until requestIdleCallback fires.
  • hydrateOnVisible(opts?) — wait until an IntersectionObserver reports the root element visible.
  • hydrateOnMediaQuery(query) — hydrate only when matchMedia(query) matches.
  • hydrateOnInteraction(events) — hydrate on the first matching event (click, pointerover, etc.).

A lazy-hydrated component renders a placeholder (the SSR markup is left untouched) until the strategy fires, then the real component mounts and binds.

This is invaluable for "islands of interactivity" patterns: a 100-component page can be statically rendered, with only the components the user actually interacts with becoming reactive.

Teleport hydration

Teleports are tricky because their target is by CSS selector, not by DOM tree position. compiler-ssr renders the teleport's children into the SSR string at the teleport's logical position wrapped in <!--teleport start--> / <!--teleport end--> markers; the server caller is expected to splice those into the right place in the final HTML by reading ssrContext._teleports. Hydration on the client uses Teleport.hydrate (packages/runtime-core/src/components/Teleport.ts) which:

  1. Walks from the teleport's starting marker to its end marker.
  2. Locates the actual DOM target via target selector / element.
  3. Verifies the children match what the vnode expects.

If the markers are missing (server-side teleports weren't merged), hydration falls back to a client-side teleport mount.

Suspense hydration

Suspense.hydrate decides whether the SSR rendered the default slot (success) or the fallback slot (timeout). It uses a marker comment whose content indicates the choice. If the SSR rendered fallback, the client repeats the wait, then replaces fallback with the resolved default slot once dependencies finish.

Mismatch reporting

Two flags control mismatch behavior:

  • __DEV__ — full mismatch warnings, including a structured payload of expected vs actual.
  • __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ — keeps detailed payloads in production at the cost of bundle size. Default false.

The logMismatchError function in hydration.ts is the single place mismatch payloads are formatted.

Files to know

  • packages/runtime-core/src/hydration.ts
  • packages/runtime-core/src/hydrationStrategies.ts
  • packages/runtime-core/src/renderer.ts (search for createHydrationRenderer)
  • packages/runtime-core/src/components/Teleport.ts (Teleport.hydrate)
  • packages/runtime-core/src/components/Suspense.ts (Suspense.hydrate)
  • packages/runtime-dom/src/index.ts (createSSRApp, hydrate exports)
  • packages/runtime-dom/src/directives/vModel.ts (getSSRProps, initVModelForSSR)
  • packages/runtime-dom/src/directives/vShow.ts (initVShowForSSR)

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

Hydration – Vue.js wiki | Factory