Lazy Hydration in Nuxt
This guide is part of Vue & Nuxt Performance, within Framework Performance. A server-rendered Nuxt page arrives as complete HTML, but it is not interactive until hydration has run: Vue loads the components' JavaScript, runs their setup, rebuilds state and attaches event listeners. By default, every component on the page hydrates immediately after load — the header, the hero, the product description, the reviews section, the recommendations carousel and the footer — in one burst of main-thread work.
Most of that work is unnecessary at load time. Components below the fold will not be used for seconds, and some (static text, footers) never need interactivity. Lazy hydration delays hydrating a component until a trigger: when it becomes visible, when the browser is idle, when the user interacts with it, after a delay, or when a media query matches. Server components go further, rendering on the server with no client JavaScript at all. Together they shrink the hydration burst, reducing TBT and the INP of early interactions.
Rapid Diagnosis
- Record a load trace with CPU throttling; find the hydration long task after the app bundle runs.
- List components on the page and mark which are interactive and above the fold.
- Check for static content components (footers, SEO text, legal blocks) that hydrate.
- Check bundle chunks: below-the-fold components' code should be split out to benefit from lazy hydration.
Root Cause Analysis
1. Eager hydration of everything. All components hydrate at once.
2. Static content as client components. Components without interactivity still hydrate.
3. Heavy below-the-fold widgets. Carousels, maps and reviews hydrate during load.
4. Shared chunks. Lazy components imported statically are still in the main bundle.
Step-by-Step Resolution
1. Use hydration triggers on lazy components
<template>
<ProductHero :product="product" />
<LazyProductReviews hydrate-on-visible :id="product.id" />
<LazyRecommendations hydrate-on-idle />
<LazyNewsletterSignup hydrate-on-interaction="focus" />
<LazySiteFooter hydrate-never />
</template>
<!-- trade-off: before hydration, components are static HTML; clicks on a not-yet-
hydrated button do nothing unless the trigger is interaction-based. -->
Prefixing a component with Lazy splits its code into a separate chunk; the hydrate-* props control when it hydrates.
2. Make static sections server components
Rename static components to .server.vue (with server components enabled) so they render only on the server and ship no client code.
3. Choose triggers by component role
Visible for below-the-fold sections, interaction for forms and widgets used occasionally, idle for content that should become interactive soon without competing with load, never for static content.
4. Reserve space
Lazy-hydrated components are server-rendered, so their layout is present; client-only components (<ClientOnly>) need sized placeholders to avoid CLS.
Verification
Record a load trace again: the hydration long task should be shorter, with later small tasks as components hydrate on their triggers. Scroll and interact to confirm components hydrate and work. In the field, compare TBT-related metrics and INP for early interactions.
Worked Example: A Travel Guide Site
A travel guide site's destination pages rendered a hero, an article with an interactive table of contents, a photo gallery, a map, a weather widget, reviews, related destinations and a large footer. Hydration took 900ms on a mid-range phone; INP for tapping the table of contents during the first seconds was over 400ms. The team made the article body and footer server components, lazy-hydrated the gallery and map on visibility, the weather widget on idle, and reviews on visibility. The initial hydration task dropped to 220ms, TBT by 650ms, and INP p75 from 290ms to 160ms.
How Lazy Hydration Interacts With Client Navigation
On client-side navigation, components render on the client rather than hydrating server HTML. Lazy components still load their code on demand, but hydration triggers apply mainly to the initial server-rendered page. Make sure lazy components render correctly in both modes, and test navigations as well as first loads. Components marked hydrate-never still render on client navigation (they have no server HTML to keep), so they should be lightweight or replaced with server components.
Measuring the Effect
Hydration improvements show most clearly in lab traces (shorter initial long task, lower TBT) and in field INP for interactions during the first seconds. Use Long Animation Frames attribution to confirm that hydration frames shrank. Track the number of components hydrated on load as a simple metric in lab tests; it is a good leading indicator before field data catches up.
Rolling Out Lazy Hydration Safely
Start with components that are clearly below the fold on every viewport and have no early interactions: reviews, related content, footers. Apply hydrate-on-visible or convert them to server components, then test scrolling and interacting on a throttled device. Next, look at widgets that users rarely touch (newsletter forms, share menus) and use interaction triggers. Leave above-the-fold controls eager. After each step, compare the hydration trace and check for regressions such as buttons that appear but do not respond, or content that changes when it hydrates (a sign of hydration mismatches).
Common Mistakes
- Lazy-hydrating above-the-fold controls. Users click and nothing happens.
- Static imports of lazy components. Code stays in the main bundle.
- hydrate-never on interactive components. They never work.
- No placeholders for client-only components. Layout shifts.
Edge Cases
Nuxt version. Built-in hydration triggers are available in recent Nuxt versions; older versions need community modules.
Third-party components. Wrap them in lazy components to defer both code and hydration.
State shared across lazy components. State initialised during SSR is in the payload; lazy components read it when they hydrate.
Accessibility. Ensure non-hydrated content is accessible as static HTML (links work, content readable).
FAQ
What is lazy hydration?
Delaying the hydration of a server-rendered component until a trigger, such as visibility, idle time or interaction, instead of hydrating it immediately on load.
Does lazy hydration hurt SEO?
No. The HTML is still server-rendered; only the JavaScript activation is delayed.
What happens if a user clicks before hydration?
With visibility or idle triggers, the click may do nothing until hydration completes. Use interaction triggers for components users may click early.
What are Nuxt server components?
Components (.server.vue) rendered only on the server, sending HTML without client JavaScript. They cannot have client interactivity.
Is lazy hydration the same as lazy loading components?
Related but different. Lazy loading defers the component's code; lazy hydration also defers running it on server-rendered HTML.
Does lazy hydration improve LCP?
Usually little, since LCP depends on server HTML and resources. It mainly improves TBT and INP.
Can I lazy-hydrate based on screen size?
Yes, with a media query trigger, for example hydrating a desktop-only sidebar only on large screens.
How do I know which components to make lazy?
Start with components below the fold and heavy widgets; check the hydration trace and bundle analysis for the largest contributors.
Does lazy hydration change the HTML?
No. Components are still server-rendered; only the client-side activation is deferred.
Can lazy hydration cause hydration mismatches?
Not by itself. Mismatches come from differences between server and client rendering, such as dates or random values; fix those regardless of hydration timing.
Related
- Reducing Nuxt payload size — less state to revive.
- Choosing Astro client directives — the islands version of the same idea.
- Deferring components with Angular @defer blocks — Angular's equivalent.