Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/async-component-suspense-control.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

2.4 KiB

Async Components Are Suspensible by Default

Rule

Async components created with defineAsyncComponent are automatically treated as async dependencies of any parent <Suspense> component. When wrapped by <Suspense>, the async component's own loadingComponent, errorComponent, delay, and timeout options are ignored.

Why This Matters

This behavior causes confusion when developers configure loading and error states on their async components but these states never appear because a parent <Suspense> takes over control. The component's options are silently ignored, leading to unexpected behavior.

Bad Code

<script setup>
import { defineAsyncComponent } from 'vue';

// These options will be IGNORED if a parent Suspense exists
const AsyncDashboard = defineAsyncComponent({
  loader: () => import('./Dashboard.vue'),
  loadingComponent: LoadingSpinner, // Won't show!
  errorComponent: ErrorDisplay, // Won't show!
  timeout: 3000, // Ignored!
});
</script>

<template>
  <!-- If this is inside a Suspense somewhere up the tree -->
  <AsyncDashboard />
</template>

Good Code

<script setup>
import { defineAsyncComponent } from 'vue';

// Use suspensible: false to keep control of loading/error states
const AsyncDashboard = defineAsyncComponent({
  loader: () => import('./Dashboard.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  timeout: 3000,
  suspensible: false, // Component controls its own loading state
});
</script>

<template>
  <AsyncDashboard />
</template>

When to Use Each Approach

Keep suspensible (default) when:

  • You want centralized loading/error handling at a layout level
  • The parent <Suspense> provides appropriate feedback
  • Multiple async components should show a unified loading state

Use suspensible: false when:

  • You need component-specific loading indicators
  • The component should handle its own error states
  • You want fine-grained control over the UX

Key Points

  1. Check if your component tree has a <Suspense> ancestor before relying on async component options
  2. Use suspensible: false explicitly when you need the component to manage its own states
  3. The <Suspense> component's #fallback slot and onErrorCaptured take precedence over async component options

References