Vue cookbook · Advanced
Lazy-load and lazily hydrate heavy components
A chart library and comments widget make the initial page slow, even though they're below the fold.
Approach
Wrap heavy components with defineAsyncComponent (with loading and error components and a timeout). In SSR apps,
add a hydration strategy (Vue 3.5) such as hydrateOnVisible() so their JavaScript only hydrates when needed.
Solution
// components/lazy.ts
import { defineAsyncComponent, hydrateOnIdle, hydrateOnVisible } from "vue";
import LoadingBlock from "./LoadingBlock.vue";
import ErrorBlock from "./ErrorBlock.vue";
export const SalesChart = defineAsyncComponent({
loader: () => import("./SalesChart.vue"),
loadingComponent: LoadingBlock,
errorComponent: ErrorBlock,
delay: 200, // avoid flashing the loader for fast loads
timeout: 10000,
hydrate: hydrateOnVisible({ rootMargin: "200px" }),
});
export const CommentsWidget = defineAsyncComponent({
loader: () => import("./CommentsWidget.vue"),
hydrate: hydrateOnIdle(),
});
Pitfalls
- Lazy-loading components that are visible on first paint, delaying the main content.
- No error component, leaving a blank area when a chunk fails to load.
- Expecting hydration strategies to help in client-only apps; they apply to server-rendered HTML.
Variations
- In Nuxt, prefix components with
Lazy(<LazySalesChart hydrate-on-visible />). hydrateOnInteraction("click")defers hydration until the user interacts.
Further reading: Async components and lazy hydration
Reference: https://vuejs.org/guide/components/async.html#lazy-hydration
More Vue recipes
- Access child component methods with template refs and defineExpose
- Add SEO metadata and structured data in Nuxt
- Build an accessible modal with Teleport
- Build custom inputs with defineModel and modifiers
- Catch component errors with an error boundary
- Debounce user input with a custom ref
- Fetch data in a composable and cancel stale requests
- Fetch, refresh and share data with Nuxt useFetch