In Vue 3.5, what does each built-in hydration strategy wait for, and how do you choose between them?
answer
- idle, visible, media, interaction
- a timeout caps idle
- observer options for visible
- interaction replays the event
basics
~20 shydrateOnIdle waits for idle time (10 s cap by default), hydrateOnVisible for a root entering the viewport, hydrateOnMediaQuery for a matching query, hydrateOnInteraction for a listed event, which it replays. Choose by when the user first needs it.
solid answer
~40 s`hydrateOnIdle(timeout?)` uses `requestIdleCallback` with a maximum wait that defaults to 10,000 ms, falling back to a short `setTimeout` where idle callbacks are missing: good for non-urgent widgets that should become live soon anyway. `hydrateOnVisible(options?)` observes the component's root elements with an `IntersectionObserver`, hydrating immediately if one is already on screen; pass `rootMargin` to start early. It suits below-the-fold content. `hydrateOnMediaQuery(query)` hydrates now if the query matches, otherwise on the first change: a mobile-only drawer. `hydrateOnInteraction(events)` listens on the root elements, hydrates on the first listed event and replays it: rarely used controls. The first interaction pays the hydration latency, and the event must reach a root element.
code
ts · 31 linesimport {
defineAsyncComponent,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
} from 'vue'
// Non-urgent: live within 5 s even if the page stays busy.
export const RecommendedPosts = defineAsyncComponent({
loader: () => import('./RecommendedPosts.vue'),
hydrate: hydrateOnIdle(5000),
})
// Below the fold: start hydrating 200px before it scrolls in.
export const RelatedProducts = defineAsyncComponent({
loader: () => import('./RelatedProducts.vue'),
hydrate: hydrateOnVisible({ rootMargin: '200px' }),
})
// Only relevant in the narrow layout.
export const MobileDrawer = defineAsyncComponent({
loader: () => import('./MobileDrawer.vue'),
hydrate: hydrateOnMediaQuery('(max-width: 768px)'),
})
// Rarely used: hydrate on first click or keyboard focus, then replay it.
export const SharePicker = defineAsyncComponent({
loader: () => import('./SharePicker.vue'),
hydrate: hydrateOnInteraction(['click', 'focusin']),
})go deeper
Recall the four strategy names and the signal each waits for: idle, visible, media query, interaction.
Explain the details: the idle timeout default, immediate hydration of on-screen roots, the first-change rule, and event replay.
Match each component to the moment the user first needs it and weigh the first-interaction latency against earlier hydration.
Set guidance for which strategy each kind of page section uses, so deferral is predictable rather than chosen ad hoc.
## The four strategies Vue 3.5 ships four strategy factories, each imported from `vue` and passed to `defineAsyncComponent({ loader, hydrate })`. Each answers the same question - when should this component's server HTML become live? - with a different signal. | Strategy | Argument | Hydrates when | Typical fit | |---|---|---|---| | `hydrateOnIdle` | optional max timeout, default 10,000 ms | the browser has idle time, or the timeout passes | non-urgent widgets that should be live soon | | `hydrateOnVisible` | optional `IntersectionObserver` options | a root element is in the viewport | below-the-fold sections | | `hydrateOnMediaQuery` | a media query string | the query matches | layout-specific components | | `hydrateOnInteraction` | one event name or an array | a listed event fires on a root element | rarely used controls | ## Details that decide the choice **`hydrateOnIdle`** - Calls `requestIdleCallback` with the timeout as its maximum wait, so the component hydrates even on a page that never goes idle. - Where `requestIdleCallback` is not available, Vue falls back to a `setTimeout` of 1 ms, which in practice hydrates almost at once. **`hydrateOnVisible`** - Checks each root element first: if one is already inside the viewport, it hydrates **immediately** instead of waiting for the observer. - Otherwise it observes the roots and hydrates on the first intersection. Passing `{ rootMargin: '200px' }` starts hydration before the component actually scrolls in, so the cost is paid before the user arrives. **`hydrateOnMediaQuery`** - Evaluates the query at hydration time: hydrates now if it matches, otherwise waits for the first change event. - Useful when a component only matters in one layout, such as a mobile navigation drawer on a desktop visit. **`hydrateOnInteraction`** - Attaches listeners for the listed events to the component's **root elements** and hydrates on the first one. - After hydrating, it **replays** the triggering event on its original target, so the newly attached handler sees the click. - The events must reach a root element. Bubbling events such as `click`, `mouseover` or `focusin` work from anywhere inside; `focus` does not bubble, so it only fires for a root that is itself focused. ## How to choose 1. **Ask when the user first needs it.** Needed soon but not first: idle. Needed when seen: visible. Needed only in one layout: media query. Needed only when touched: interaction. 2. **Weigh the first-use latency.** With interaction, the user's first click pays the hydration cost before the replayed event is handled. For a heavy component that delay can be noticeable; visible or idle pays it earlier. 3. **Keep the inert window short where it matters.** A form the user may start typing in should not wait for a rare signal. 4. **Combine when one signal is not enough** with a custom `HydrationStrategy` - for example idle, or interaction if that comes first. ## Common mis-picks - **`hydrateOnInteraction` on a form field.** The user's first keystroke or focus pays the hydration cost, and typing into an inert input before it hydrates is easy to get wrong. Visible or idle is safer for inputs. - **`hydrateOnVisible` on something above the fold.** It hydrates immediately, so the option adds code without deferring anything. - **`hydrateOnIdle` with a very long timeout** on a component users reach quickly. On a busy page, idle time may not come before the user does. - **`hydrateOnMediaQuery` as a responsive toggle.** It hydrates once, on the first match; it is not a way to switch a component on and off as the viewport changes. ## What none of them change - The component's **server HTML** is the same, and it is visible from the start. - The component's **code chunk** is still requested while the page hydrates in Vue 3.5 core; the strategies time the hydration work, not the download. - **Client-side mounts** ignore the strategy entirely, because there is no server HTML to keep.
- Why might hydrateOnInteraction('focus') never fire for a component whose root is a div containing an input?The listeners are attached to the component's root elements, and `focus` does not bubble, so focusing the inner input never reaches the div. Use `focusin`, which bubbles, or list an event that reaches the root such as `click`.
- What happens with hydrateOnIdle() in a browser without requestIdleCallback?Vue falls back to `setTimeout` with a 1 ms delay, so the component hydrates almost immediately after the page hydrates. The deferral is then minimal, which is safe but means the strategy saves little in that browser.
saying these in an interview costs you the question
- hydrateOnIdle can wait forever on a busy page.
- hydrateOnVisible always waits for the observer, even for on-screen roots.
- hydrateOnInteraction drops the first click, so users must click twice.
- Any event inside the component triggers hydrateOnInteraction, bubbling or not.
- hydrateOnMediaQuery re-hydrates every time the query toggles.