Async Components
defineAsyncComponent lazily loads a component, returning a placeholder until the chunk arrives. It is Vues code-splitting primitive — use it for routes, modals, charts, anything that does not need to be in the first JS payload. Combine with Suspense for declarative loading boundaries.
Async components with loading + error states
EXAMPLE
<script setup lang='ts'>
import { defineAsyncComponent, ref } from 'vue';
// 1) Minimal: lazy import a component
const Chart = defineAsyncComponent(() => import('./HeavyChart.vue'));
// 2) Full options — fallback UI + error handler + delay before showing loading
const Editor = defineAsyncComponent({
loader: () => import('./RichTextEditor.vue'),
loadingComponent: () => import('./Skeleton.vue').then((m) => m.default),
errorComponent: () => import('./LoadFailed.vue').then((m) => m.default),
delay: 150, // do NOT show loading state if the chunk arrives in <150ms
timeout: 8000, // give up after 8s -> show errorComponent
onError(err, retry, fail, attempts) {
if (attempts <= 2) retry(); // auto-retry transient network errors twice
else fail();
},
});
// 3) Route-level code splitting with Vue Router
// const routes = [
// { path: '/orders', component: () => import('./pages/Orders.vue') },
// ];
const showEditor = ref(false);
</script>
<template>
<!-- 4) Suspense around async children — declarative skeleton + error boundary -->
<Suspense>
<template #default>
<Chart />
</template>
<template #fallback>
<Skeleton h='240px' />
</template>
</Suspense>
<button @click='showEditor = true'>Open editor</button>
<Suspense v-if='showEditor'>
<template #default>
<Editor />
</template>
<template #fallback>
<div>Loading editor...</div>
</template>
</Suspense>
</template>
<!-- 5) Prefetch on idle — warm the chunk before the user clicks -->
<script setup lang='ts'>
import { onMounted } from 'vue';
onMounted(() => {
// Modern browsers expose requestIdleCallback; fallback to setTimeout
const idle = (window as any).requestIdleCallback ?? setTimeout;
idle(() => import('./RichTextEditor.vue'));
});
</script>
<!-- 6) Inline Suspense + AsyncComponent is the typical 'data-fetching component' pattern -->
<!-- The async setup() in a child returns a promise; Suspense waits on it. -->
<!-- /pages/Orders.vue -->
<!-- <script setup> -->
<!-- const orders = await fetch('/api/orders').then(r => r.json()); -->
<!-- </script> -->
Why it matters
Set delay around 150–200 ms so fast chunks do not flash a spinner the user can barely see. Pair it with a timeout + auto-retry in onError so a transient CDN blip turns into "one retry, recovered" instead of a permanent error UI.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { defineAsyncComponent } from 'vue';
const Heavy = defineAsyncComponent(() => import('./Heavy.vue'));
Try it Yourself »
Discussion
Loading…