skip to content

A Vue 3.5 SSR article page has a below-the-fold comments widget; what do you gain and give up by hydrating it with hydrateOnVisible()?

level: seniorimportance: should knowfreq 25%

answer

  1. main thread first for the article
  2. the chunk still downloads
  3. inert until it scrolls in
  4. props updated early cancel it

basics

~20 s

You move the widget's setup, render and listener work off the initial hydration, so the article becomes interactive sooner. You do not save its download, it stays inert until visible, and updating its props early abandons the deferral.

solid answer

~50 s

With `hydrate: hydrateOnVisible()`, the initial hydration skips the widget: no `setup()`, no render, no DOM walk and no listeners for what may be the heaviest component on the page, so the article above it is interactive sooner. The comments are still readable, because the server rendered them. What I don't gain: in Vue 3.5 core the widget's loader runs while the page hydrates, so its chunk is still downloaded; only the hydration work waits. What I give up: the widget is inert until it enters the viewport, and hydration then runs in one piece, which can stutter while scrolling if the widget is heavy. I pass `rootMargin` to start early. And if the page updates the widget's props before it hydrates, Vue skips the deferred hydration, with a dev warning, so its props should stay stable until then.

code

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

defineProps<{ postId: string }>()

const CommentsWidget = defineAsyncComponent({
  loader: () => import('./CommentsWidget.vue'),
  // Start hydrating 300px before the widget scrolls into view.
  hydrate: hydrateOnVisible({ rootMargin: '300px' }),
})
</script>

<template>
  <article>
    <slot />
  </article>
  <!-- Keep these props stable until the widget hydrates. -->
  <CommentsWidget :post-id="postId" />
</template>

go deeper

for a junior

Recall that hydrateOnVisible hydrates the widget when it scrolls into view and that its HTML is readable before then.

for a middle

Explain what is skipped at load (setup, render, DOM walk, listeners) and what still happens (the chunk request).

for a senior

Weigh the inert window, scroll-time hydration cost and the prop-update cancellation, and verify the gain in a profile.

for a principal

Frame it as ordering work on the main thread, and decide separately whether the bytes should ship at all.

## The situation An article page is server-rendered. The article, header and share bar are above the fold; a comments widget with a reply form, reactions and sorting sits at the bottom. Hydrating everything at once makes the browser run the widget's `setup()`, render its tree and walk its DOM before the page is fully interactive, even though most readers never scroll that far. ```ts const CommentsWidget = defineAsyncComponent({ loader: () => import('./CommentsWidget.vue'), hydrate: hydrateOnVisible({ rootMargin: '300px' }), }) ``` ## What you gain - **Less work in the initial hydration.** The widget's `setup()`, render, DOM matching and listener attachment are skipped until it approaches the viewport. - **Earlier interactivity for what is on screen.** The article's own components hydrate without competing with the widget for the main thread. - **Nothing lost for readers.** The comments are in the server HTML, so they are readable before hydration. - **Deferred side effects.** Anything the widget starts in `onMounted`, such as a live-update connection, starts only when it hydrates. ## What you do not gain In Vue 3.5 core, lazy hydration is **not lazy loading**. When the page hydrates, the async wrapper's setup calls the loader, and the strategy is installed once the chunk has resolved. So: 1. The widget's JavaScript is still **requested during page hydration** and competes for bandwidth. 2. Only the **hydration work** waits for visibility. If the download itself is the problem, lazy hydration alone does not fix it. ## What you give up | Cost | What it looks like | Mitigation | |---|---|---| | inert window | the reply button does nothing until the widget hydrates | hydrate early with `rootMargin` | | hydration on scroll | a heavy widget hydrates in one piece while the user scrolls | keep the widget lean; start early | | prop updates before hydration | Vue skips the deferred hydration and warns in dev | keep the widget's props stable until it is live | | more moving parts | two code paths: hydrated late on first load, mounted normally on client navigation | test both paths | The prop-update rule comes from the runtime: if the async wrapper is updated before its strategy fires, Vue skips the lazy hydration, warning in development that the component 'was updated before lazy hydration performed', and the component is brought up to date through the normal update instead. A page that, say, pushes a new comment count into the widget right after mount therefore quietly loses the deferral. ## Measuring the gain Claims about hydration savings should be checked, not assumed: - Record a page-load profile before and after, with CPU throttling, and compare how long the main thread is busy during hydration. - In development builds, enabling `app.config.performance` adds per-component render and hydrate marks to the browser's performance timeline, which shows the widget's hydrate cost moving later. - Check the network panel: the widget's chunk should still appear during load, confirming that only the work moved. ## Decision checklist - Is the widget **below the fold for most visitors**? If it is usually on screen, `hydrateOnVisible` hydrates it immediately and saves nothing. - Is its **hydration cost** measurable in a profile? Deferring a tiny component adds complexity for no gain. - Can users **interact before it is visible**, for example through an anchor link to `#comments`? Then the page lands with the widget in view, and it hydrates at once - which is the right behaviour. - Does anything **update its props** during initial load? Stabilise them first. - Would a **custom strategy** fit better, such as hydrating on idle or on the first interaction, whichever comes first? The senior-level point is to describe lazy hydration precisely: it reorders main-thread work, it does not remove bytes, and it creates a short window in which visible UI does not respond.

  • The team says hydrateOnVisible will cut the page's JavaScript download. Is that right in Vue 3.5 core?
    No. During page hydration the async component's loader runs, and the strategy is installed after the chunk resolves. The download still happens up front; what is deferred is running the component and hydrating its DOM. Cutting bytes needs a different approach, such as not server-rendering the widget and loading it on demand.
  • Why is rootMargin worth setting for this widget?
    Without it, hydration starts only when the widget's root intersects the viewport, so its cost lands exactly as the reader arrives. A positive `rootMargin` such as `'300px'` enlarges the observed area, starting hydration a little earlier so the widget is usually live by the time it is on screen.

saying these in an interview costs you the question

  • Lazy hydration stops the widget's JavaScript from downloading until it is visible.
  • The comments are invisible until the widget hydrates.
  • Passing new props to the widget early just hydrates it with the new values.
  • A widget that is usually on screen still benefits from hydrateOnVisible.
  • The strategy also delays the widget after client-side navigation.