0Pricing
Vue Academy · Lesson

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 onErrorCaptured in a parent boundary
  • timeout avoids spinner flash; events like @resolve aid 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.

All lessons in this course

  1. Vue Suspense Component
  2. Async Components with defineAsyncComponent
  3. Streaming SSR with renderToWebStream
  4. Deferred Hydration Strategies
← Back to Vue Academy