Async Components
|
This section documents the current Vue 3.x release line as published at
the official Vue.js documentation, which is the reference these pages are written and
verified against. No specific patch version is pinned. The Composition API with This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vue and its ecosystem iterate quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
An async component is defined by a function that returns a Promise resolving to a component. Vue does not request its code until the component is first rendered, which lets a bundler split it into a separate chunk loaded on demand.
defineAsyncComponent
Wrap a dynamic import() in defineAsyncComponent. The result is a normal component you register and use
like any other:
import { defineAsyncComponent } from 'vue'
const AsyncDashboard = defineAsyncComponent(() =>
import('./components/Dashboard.vue')
)
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncDashboard = defineAsyncComponent(() => import('./components/Dashboard.vue'))
</script>
<template>
<AsyncDashboard v-if="showDashboard" />
</template>
The loader runs once; the resolved component is cached for every later use. See
Async Components and
defineAsyncComponent().
The options object
Passing an options object instead of a bare loader controls what renders while loading and on failure:
const AsyncDashboard = defineAsyncComponent({
loader: () => import('./components/Dashboard.vue'),
loadingComponent: LoadingSpinner,
delay: 200, // ms to wait before showing loadingComponent (default 200)
errorComponent: LoadError,
timeout: 8000, // ms after which the load counts as failed (default: never)
onError(error, retry, fail, attempts) {
if (error.message.includes('fetch') && attempts <= 3) {
retry() // re-run the loader
} else {
fail()
}
}
})
-
delayprevents a spinner flash on fast connections —loadingComponentappears only if the load is still pending after that many milliseconds. -
timeouttriggerserrorComponent(and theonErrorhandler) if the loader has not resolved in time. -
onErrorreceivesretryandfailcallbacks plus the attempt count, giving a bounded retry loop for transient network errors.
Route-level code splitting
A router is the most common split point: give a route a lazy component and that route’s code — with its child components — forms its own chunk, fetched when the user first navigates there.
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/reports', component: () => import('./views/Reports.vue') }
]
})
Vue Router accepts the () ⇒ import(…) loader directly, so defineAsyncComponent is not needed for route
components — it is for lazily loaded components used inside a view. See
Lazy Loading Routes.
Pairing with Suspense
By default each async component manages its own loading and error UI. Wrapping one — or a tree of them — in
the experimental <Suspense> built-in hoists that to a single boundary: one fallback while any nested
async dependency (async components, and components with an async setup()) is still pending.
<template>
<Suspense>
<template #default>
<AsyncDashboard />
</template>
<template #fallback>
<p>Loading dashboard...</p>
</template>
</Suspense>
</template>
Under <Suspense> a component’s own loadingComponent is ignored — the #fallback template takes over — while errorComponent still handles a failed load and an onErrorCaptured hook on a parent can catch what is
left. <Suspense>, <KeepAlive>, and <Teleport> are covered together in
KeepAlive, Teleport, and Suspense. See
Suspense.
See also
-
KeepAlive, Teleport, and Suspense —
<Suspense>in full, plus<KeepAlive>and<Teleport>. -
Registration and Props — registering the component an async loader resolves to.
-
React Reference —
React.lazyand<Suspense>as the same pattern elsewhere.