<Suspense> Boundary and defineAsyncComponent() Loading Orchestration
- Last updated
- Prerequisites:
- Vue 3 `<script setup>` with top-level `await`
- Dynamic imports `import()`
- vue
- suspense
- defineAsyncComponent
- async
- code-splitting
- loading
Read at your depth
The practical view
`<Suspense>` is a built-in component that orchestrates nested asynchronous dependencies in the component tree. It provides two slots: `#default` (your async components) and `#fallback` (the loading indicator). An async dependency is any component using top-level `await` in `<script setup>` or loaded via `defineAsyncComponent(() => import('./Child.vue'))`. `<Suspense>` waits for all nested async children to resolve before revealing the tree.
The same idea in other frameworks
`<Suspense fallback={<Loading />}><AsyncChild /></Suspense>` catching thrown promises.`@defer (on immediate) { <AsyncChild /> } @loading { <Loading /> }`.`<Suspense><template #default><AsyncChild /></template><template #fallback><Loading /></template></Suspense>`.`{#await asyncPromise}<Loading />{:then data}<AsyncChild {data} />{/await}`.Legacy vs modern
Per-Component Loading Spinners vs Coordinated <Suspense>
Multiple independent loading spinners create visual layout thrashing; `<Suspense>` coordinates nested async children into one fallback.
<div v-if="loading">Spinner</div><div v-else>Content</div> // ❌ Staggered pop-in<Suspense><template #default><Dashboard /></template><template #fallback><Skeleton /></template></Suspense> // ✅ NEWInterview gotchas
Context
Interviewers test your understanding of what triggers the Suspense boundary.
The mechanical answer
A component is treated as an async dependency by `<Suspense>` if: (1) it uses **top-level `await`** inside `<script setup>`, (2) its `setup()` hook returns a `Promise`, or (3) it is loaded via **`defineAsyncComponent()`**.
Trap
A component that performs data fetching inside `onMounted(async () => ...)` is NOT an async dependency for Suspense because `setup()` finishes synchronously.
Context
Checks knowledge of error handling alongside Suspense.
The mechanical answer
Use the **`onErrorCaptured`** lifecycle hook in the parent component or wrap the `<Suspense>` boundary in an Error Boundary component. When an async component fails, `onErrorCaptured` intercepts the error and allows you to set an error state to display a fallback error view.
Trap
`<Suspense>` only manages loading/fallback states; it does *not* catch runtime errors or rejected promises on its own.