Lazy-Load Components — Smaller Bundles
Async Components
defineAsyncComponent loads a component on demand with loading and error fallbacks. Use it for heavy widgets the user might never open.
What you'll learn
- Wrap a dynamic import with defineAsyncComponent
- Add loadingComponent and errorComponent
- Combine with Suspense
Async components defer loading a component’s code until it is actually rendered. Bundlers split it into a separate chunk, so the initial download stays small.
Basic Async Component
import { defineAsyncComponent } from 'vue'
export const Heavy = defineAsyncComponent(
() => import('./Heavy.vue')
) Use it like any normal component. The first time it renders, Vue fetches the chunk; subsequent uses are instant.
Loading and Error Fallbacks
The options form gives you fallback components and timing knobs.
import { defineAsyncComponent } from 'vue'
import Spinner from './Spinner.vue'
import ErrorFallback from './ErrorFallback.vue'
export const Editor = defineAsyncComponent({
loader: () => import('./Editor.vue'),
loadingComponent: Spinner,
errorComponent: ErrorFallback,
delay: 200, // wait 200ms before showing the spinner
timeout: 5000, // after 5s, show the error fallback
}) The delay avoids a spinner flash for fast network responses. The timeout protects against stuck loads.
Pairing With Suspense
If your async component also uses top-level await in <script setup>, wrap it in Suspense so Vue can coordinate the loading state.
<template>
<Suspense>
<template #default>
<Dashboard />
</template>
<template #fallback>
<p>Loading dashboard…</p>
</template>
</Suspense>
</template> Suspense waits for all async dependencies in its tree before showing #default. Use it sparingly — usually one near the route level is enough.
Form Inputs — v-model Across Every Input Type →