skip to content

In a Vue 3.5 SSR app, what is lazy hydration, and how do you enable it for one component?

level: juniorimportance: should knowfreq 30%

answer

  1. async components only
  2. an option next to loader
  3. a strategy decides when
  4. HTML visible, not yet live

basics

~10 s

Lazy hydration lets a server-rendered async component stay as inert server HTML until a strategy tells Vue to hydrate it. Enable it with defineAsyncComponent's hydrate option, for example hydrate: hydrateOnVisible().

solid answer

~40 s

Normally the client hydrates the whole server-rendered tree in one pass: it runs every component's `setup()`, renders, and attaches listeners to the existing DOM. Since Vue 3.5 an async component can defer its part: pass a strategy to the `hydrate` option of `defineAsyncComponent`, alongside `loader`, for example `hydrate: hydrateOnIdle()`. Until the strategy calls back, the component's server HTML stays on screen but is inert - its `setup()` has not run, it has no event listeners and it does not react to state. The built-in strategies `hydrateOnIdle`, `hydrateOnVisible`, `hydrateOnMediaQuery` and `hydrateOnInteraction` are imported individually from `vue`, so unused ones are tree-shaken. The option matters only during hydration; mounted later on the client, the component behaves like any async component.

code

vue · 16 lines
vue
<script setup lang="ts">
import { defineAsyncComponent, hydrateOnIdle } from 'vue'

const NewsletterSignup = defineAsyncComponent({
  loader: () => import('./NewsletterSignup.vue'),
  // Keep the server HTML inert until the browser is idle (at most 10 s by default).
  hydrate: hydrateOnIdle(),
})
</script>

<template>
  <main>
    <slot />
  </main>
  <NewsletterSignup />
</template>

go deeper

for a junior

Remember that the hydrate option of defineAsyncComponent defers hydration, and that the server HTML is visible but not interactive until then.

for a middle

Explain what has and has not run before the strategy fires, and that the option applies only during SSR hydration.

for a senior

Pick which components to defer from what the user needs first, and account for the inert window and the still-eager chunk request.

for a principal

Decide where deferral pays for its complexity: a few heavy, non-critical components, not a blanket rule for every async component.

## Hydration in one paragraph With server-side rendering, the server sends HTML that already shows the page. The client then **hydrates** it: it creates the app with `createSSRApp`, runs each component's `setup()`, renders the virtual DOM, walks the existing DOM to match it, and attaches event listeners. Only after hydration does the page respond to clicks and state changes. By default Vue hydrates the whole tree in one pass as the app mounts. ## What lazy hydration changes Since **Vue 3.5**, an async component can postpone its own hydration. You give `defineAsyncComponent` a **hydration strategy** through its `hydrate` option. The strategy is a function that decides when to call back; until it does, Vue leaves that component's part of the server HTML alone: - the HTML is **visible**, because the server rendered it; - the component's `setup()` and lifecycle hooks have **not run**; - there are **no event listeners**, so buttons and inputs inside do nothing; - the component **does not react** to state changes yet. When the strategy fires, Vue hydrates the component normally and it becomes live. ## How to enable it 1. Define the component with the **object form** of `defineAsyncComponent`, because `hydrate` sits next to `loader`. 2. Import a strategy from `vue`: `hydrateOnIdle`, `hydrateOnVisible`, `hydrateOnMediaQuery` or `hydrateOnInteraction`. Each is imported individually, so the ones you do not use are **tree-shaken**. 3. Call the strategy factory and pass the result: `hydrate: hydrateOnVisible()`. Most factories take an optional argument, such as a timeout or observer options. 4. Use the component in the template as usual. ## The four built-in strategies at a glance | Strategy | Hydrates when | |---|---| | `hydrateOnIdle(timeout?)` | the browser reports idle time, or the timeout passes | | `hydrateOnVisible(options?)` | a root element enters the viewport | | `hydrateOnMediaQuery(query)` | the media query matches | | `hydrateOnInteraction(events)` | one of the listed events fires on a root element | You can also pass your own function of type `HydrationStrategy`. ## The timeline of one lazily hydrated component 1. The server renders the whole page, including the async component's HTML, and the browser paints it. 2. The client entry calls `createSSRApp(App).mount('#app')`, and Vue starts hydrating from the root. 3. When hydration reaches the async component, its wrapper starts the **loader**, so the component's code is requested now. 4. Once the code has loaded, Vue calls the **strategy**, handing it a `hydrate` callback, and moves on without hydrating that subtree. 5. The rest of the page finishes hydrating and becomes interactive. 6. Later - on idle, on visibility, on a media-query match or on an interaction - the strategy calls `hydrate`, and the component's `setup()` runs, its DOM is matched and its listeners are attached. Steps 4 to 6 are the whole feature: the component's work moves from the critical first pass to a moment the strategy chooses. ## Things that surprise people - **It only applies to hydration.** If the component is first mounted by a client-side navigation, there is no server HTML to keep, and it renders like any async component once its loader resolves. - **It is not lazy loading.** In Vue 3.5 core, the component's loader still runs while the page hydrates; what waits is the hydration work, not the network request. - **It is low-level by design.** The Vue docs describe it as an intentionally low-level API that frameworks may build syntax on; in plain Vue you write the `defineAsyncComponent` call yourself. - **Inert means inert.** A user who clicks inside the component before it hydrates gets no response, unless the strategy is `hydrateOnInteraction`, which replays the triggering event after hydrating. ## Why it is worth knowing Full hydration makes the browser do all of the page's component work before anything is interactive. Deferring components that are below the fold, rarely used or only relevant on some layouts frees the main thread for the parts the user needs first, without giving up server-rendered HTML for them.

  • Does lazy hydration apply when the component is mounted by a client-side route change?
    No. The strategy is consulted only when Vue is hydrating existing server HTML. On a client-side mount there is nothing to hydrate, so the component renders as soon as its loader resolves, like any other async component.
  • What happens if the user clicks a button inside the component before it hydrates?
    Nothing, for most strategies: the server HTML has no Vue listeners yet. `hydrateOnInteraction` is the exception for the events you list: it hydrates on the first matching event on a root element and then replays that event, so the handler sees it.

saying these in an interview costs you the question

  • Lazy hydration hides the component until the strategy fires.
  • Any component can be lazily hydrated with a directive on its tag.
  • Lazy hydration also defers downloading the component's code.
  • The hydrate option also delays components mounted by client-side navigation.
  • The built-in strategies are bundled whether or not you import them.