Vue Suspense Component
with default and #fallback slots, async component loading, nested Suspense.
Vue Suspense Component is a free Vue Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Suspense Solves
Suspense is a built-in Vue component that coordinates loading states for components that have asynchronous dependencies.
Without it, every async component manages its own spinner. With Suspense you declare one boundary that waits for all nested async work and shows a single fallback until everything is ready.
Two Slots: default and fallback
Suspense exposes two named slots:
- default — the real content, which may contain async components or an async
setup() - fallback — what to show while the default slot resolves
When all async dependencies settle, Vue swaps the fallback out for the default content.
<template>
<Suspense>
<template #default>
<UserDashboard />
</template>
<template #fallback>
<div class="spinner">Loading dashboard...</div>
</template>
</Suspense>
</template>Async setup() as a Dependency
A component becomes an async dependency when its setup() function is declared async and awaits something. Vue tracks the returned promise.
The <script setup> equivalent is using top-level await directly inside the block.
<script setup>
import { ref } from 'vue'
// Top-level await makes this an async dependency of Suspense
const res = await fetch('/api/profile')
const profile = ref(await res.json())
</script>
<template>
<h1>Welcome, {{ profile.name }}</h1>
</template>Async Components Count Too
Components created with defineAsyncComponent are also tracked by Suspense. The fallback stays visible until the dynamic import() resolves.
This lets you code-split heavy components without writing per-component loading UI.
<script setup>
import { defineAsyncComponent } from 'vue'
const Chart = defineAsyncComponent(() =>
import('./HeavyChart.vue')
)
</script>
<template>
<Suspense>
<Chart />
<template #fallback>Preparing chart...</template>
</Suspense>
</template>Multiple Async Children Wait Together
If the default slot contains several async dependencies, Suspense waits for all of them to resolve before revealing the content.
This prevents layout shift from components popping in one at a time.
<template>
<Suspense>
<div>
<UserStats /> <!-- async setup -->
<RecentOrders /> <!-- async setup -->
<Recommendations /> <!-- async component -->
</div>
<template #fallback>
<PageSkeleton />
</template>
</Suspense>
</template>Catching Rejections with onErrorCaptured
If an async setup() rejects (for example, a failed fetch), the error propagates up to the nearest onErrorCaptured hook in a parent component.
Suspense itself does not render errors, so you pair it with an error boundary.
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false // stop the error from propagating further
})
</script>Error Boundary + Suspense Pattern
The common production pattern wraps Suspense inside an error-boundary component. onErrorCaptured returns false to stop propagation and render an error UI instead.
<script setup>
import { ref, onErrorCaptured } from 'vue'
const err = ref(null)
onErrorCaptured((e) => { err.value = e; return false })
</script>
<template>
<p v-if="err">Failed to load: {{ err.message }}</p>
<Suspense v-else>
<ProfilePanel />
<template #fallback>Loading...</template>
</Suspense>
</template>The pending and resolve Events
Suspense emits lifecycle events you can listen to:
- @pending — fired when entering a pending (loading) state
- @resolve — fired when the default slot finishes resolving
- @fallback — fired when the fallback content is shown
Useful for analytics or progress bars.
<template>
<Suspense @pending="onPending" @resolve="onResolve">
<RouteView />
<template #fallback>Loading route...</template>
</Suspense>
</template>timeout Prop for Delayed Fallback
The timeout prop (in milliseconds) delays showing the fallback. If the async work resolves faster than the timeout, the fallback never appears.
This avoids a flash of spinner for fast responses.
<template>
<!-- Only show fallback if loading exceeds 200ms -->
<Suspense :timeout="200">
<FastPanel />
<template #fallback>Loading...</template>
</Suspense>
</template>Suspense with Router and Transitions
Suspense composes with RouterView and Transition to animate async route changes. The order matters: Transition wraps Suspense, which wraps the routed component.
<template>
<RouterView v-slot="{ Component }">
<Transition mode="out-in">
<Suspense>
<component :is="Component" />
<template #fallback>Loading page...</template>
</Suspense>
</Transition>
</RouterView>
</template>When NOT to Use Suspense
Avoid Suspense for data that refetches frequently while the component stays mounted — it only governs the initial async resolution. For ongoing loading states (pagination, refetch), use a normal reactive loading ref instead.
<script setup>
import { ref } from 'vue'
const loading = ref(false)
async function loadMore() {
loading.value = true
await fetchNextPage()
loading.value = false
}
</script>Quick Check
Test your understanding of Suspense.
Recap
You learned how Suspense coordinates async dependencies:
- default slot holds async content (async setup or async components)
- fallback slot shows during loading
- It waits for all async children before revealing content
- Rejections bubble to
onErrorCapturedin a parent boundary timeoutavoids spinner flash; events like@resolveaid analytics
Frequently asked questions
Is the “Vue Suspense Component” lesson free?
Yes — the full text of “Vue Suspense Component” is free to read here on the web, and the Vue Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Vue Academy course, upgrade to CoddyKit PRO.
What will I learn in “Vue Suspense Component”?
with default and #fallback slots, async component loading, nested Suspense. You practise Vue Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Vue Academy?
No prior experience is required. Vue Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Vue Suspense Component” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Vue Academy lesson?
Yes. Every Vue Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.