Skip to main content
Practice Problems

async components and suspense in Vue.js

Async Components

Async components are loaded on demand (lazy-loaded), reducing the initial bundle size. Vue provides defineAsyncComponent for this purpose.


defineAsyncComponent

typescript
import { defineAsyncComponent } from 'vue' // Basic async component const AsyncModal = defineAsyncComponent( () => import('./components/HeavyModal.vue') ) // With options const AsyncDashboard = defineAsyncComponent({ loader: () => import('./components/Dashboard.vue'), loadingComponent: LoadingSpinner, errorComponent: ErrorDisplay, delay: 200, // Delay before showing loading (ms) timeout: 10000, // Timeout before showing error (ms) })
vue
<template> <!-- Loaded only when rendered --> <AsyncModal v-if="showModal" /> <AsyncDashboard /> </template>

Suspense

<Suspense> is a built-in component for handling async dependencies — it shows fallback content while async components or async setup are loading.

vue
<template> <Suspense> <!-- Default slot: async content --> <template #default> <AsyncDashboard /> </template> <!-- Fallback slot: shown while loading --> <template #fallback> <LoadingSpinner /> </template> </Suspense> </template>

Async Setup

Components with async setup work with <Suspense>:

vue
<!-- UserProfile.vue --> <script setup> // This makes the component async — needs Suspense parent const response = await fetch('/api/user/1') const user = await response.json() </script> <template> <div> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> </div> </template>
vue
<!-- Parent.vue --> <template> <Suspense> <UserProfile /> <template #fallback> <p>Loading user...</p> </template> </Suspense> </template>

Suspense Events

vue
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback" > <AsyncComponent /> <template #fallback> <LoadingSpinner /> </template> </Suspense>

Nested Suspense

vue
<Suspense> <template #default> <Dashboard> <!-- Inner Suspense for independent loading --> <Suspense> <template #default><SlowWidget /></template> <template #fallback><WidgetSkeleton /></template> </Suspense> </Dashboard> </template> <template #fallback> <DashboardSkeleton /> </template> </Suspense>

Combining with Vue Router

vue
<template> <RouterView v-slot="{ Component }"> <Suspense> <template #default> <component :is="Component" /> </template> <template #fallback> <PageLoading /> </template> </Suspense> </RouterView> </template>

Error Handling

vue
<script setup> import { onErrorCaptured, ref } from 'vue' const error = ref<Error | null>(null) onErrorCaptured((err) => { error.value = err return false // Prevent propagation }) </script> <template> <div v-if="error">Error: {{ error.message }}</div> <Suspense v-else> <AsyncComponent /> <template #fallback><Loading /></template> </Suspense> </template>

Important:

Use defineAsyncComponent for code splitting — load heavy components on demand. Use <Suspense> to show loading states for async components and components with async setup(). Combine both with Vue Router for route-level code splitting. Suspense is still experimental in Vue 3 but widely used.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems