If you’ve ever struggled with showing loading states while waiting for API calls or lazily loading components in Vue, you’ll love Vue 3 Suspense. Suspense provides a clean way to handle asynchronous operations in your components, making your UI smoother and your code easier to manage.
Table of contents [Show]
- What is Vue 3 Suspense?
- Why Use Suspense?
- Basic Structure of Suspense
- Step 1: Creating an Async Component
- Step 2: Using Suspense with Async Setup
- Step 3: Wrapping in Suspense
- Step 4: Handling Errors in Suspense
- Real-World Example: Async Data + Notifications
- Best Practices for Suspense
- Final Thoughts
What is Vue 3 Suspense?
Suspense is a built-in component in Vue 3 that lets you handle async dependencies in components. When a component is loading data or an async resource, Suspense allows you to display fallback content such as a loading spinner until the async task resolves.
Why Use Suspense?
- Manage loading states without cluttering your components.
- Gracefully handle errors when fetching async data.
- Lazy load heavy components only when needed.
- Improve user experience with clean fallback UIs.
Basic Structure of Suspense
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<p>Loading...</p>
</template>
</Suspense>
Here’s what’s happening:
#default: The component you want to render asynchronously.#fallback: What to show while the default component is still resolving.
Step 1: Creating an Async Component
Vue provides a helper function defineAsyncComponent to load components lazily.
// AsyncComponent.js
import { defineAsyncComponent } from 'vue'
export default defineAsyncComponent(() =>
import('./UserProfile.vue')
)
This will load UserProfile.vue only when required, which is especially useful for large applications.
Step 2: Using Suspense with Async Setup
Another exciting feature in Vue 3 is async setup. You can return a Promise from setup, and Suspense will automatically wait for it.
<!-- UserProfile.vue -->
<template>
<div>
<h2>User Profile</h2>
<p>Name: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
</div>
</template>
<script setup>
async function fetchUser() {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1')
return res.json()
}
const user = await fetchUser()
</script>
Since setup is async here, Vue Suspense will automatically handle the waiting state for us.
Step 3: Wrapping in Suspense
Now we combine everything inside the parent app:
<!-- App.vue -->
<template>
<div>
<h1>Vue 3 Suspense Example</h1>
<Suspense>
<template #default>
<UserProfile />
</template>
<template #fallback>
<p>Loading user data...</p>
</template>
</Suspense>
</div>
</template>
<script setup>
import UserProfile from './UserProfile.vue'
</script>
Step 4: Handling Errors in Suspense
We can extend defineAsyncComponent with options for error handling and retries.
// AsyncWithErrorHandling.js
import { defineAsyncComponent } from 'vue'
export default defineAsyncComponent({
loader: () => import('./UserProfile.vue'),
delay: 200,
timeout: 3000,
errorComponent: {
template: '<p>Failed to load component</p>'
},
loadingComponent: {
template: '<p>Loading...</p>'
}
})
Real-World Example: Async Data + Notifications
Let’s put this into practice with a realistic use case: fetching user data with loading and error handling, while showing a notification on error.
<!-- App.vue -->
<template>
<div>
<h1>Vue 3 Suspense Real-World Example</h1>
<Suspense>
<template #default>
<UserProfile />
</template>
<template #fallback>
<div>Fetching user profile...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import UserProfile from './UserProfile.vue'
</script>
<!-- UserProfile.vue -->
<template>
<div v-if="user">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
<div v-else>
<p>No user data found</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
async function fetchUser() {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1')
if (!res.ok) throw new Error('Failed to fetch user')
return res.json()
}
const user = ref(null)
try {
user.value = await fetchUser()
} catch (err) {
console.error('Error:', err.message)
}
</script>
Now, when the UserProfile component is loading, the fallback content appears. If there’s an error, it logs it and gracefully shows “No user data found.”
Best Practices for Suspense
- Use Suspense for components that rely on async setup or API calls.
- Always provide a meaningful
fallback(spinner, skeleton loader, or message). - Combine with
defineAsyncComponentfor lazy loading routes or heavy UI pieces. - Keep error handling user-friendly.
Final Thoughts
Vue 3 Suspense is one of those features that makes your life easier as a developer while improving UX for your users. Whether you’re fetching data from APIs or lazy-loading components, Suspense ensures a smooth loading and error-handling experience. With defineAsyncComponent and async setup, you now have powerful tools to handle async workflows in a clean and declarative way.






