Files
agent-skills/skills/vue-best-practices/reference/async-component-suspense-control.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

2.5 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