Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/suspense-single-child-requirement.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.0 KiB

Suspense Slots Only Allow One Immediate Child

Rule

Both #default and #fallback slots of <Suspense> only allow for one immediate child node. Multiple root elements will cause unexpected behavior.

Why This Matters

Vue's Suspense needs to track a single root node to manage the pending/resolved state transitions. Multiple children in the default slot can cause rendering issues or the Suspense to not work correctly.

Bad Code

<template>
  <!-- Multiple children in default slot - problematic -->
  <Suspense>
    <AsyncHeader />
    <AsyncContent />
    <AsyncFooter />

    <template #fallback>
      <LoadingHeader />
      <LoadingContent />
    </template>
  </Suspense>
</template>

Good Code

<template>
  <!-- Single root wrapper in each slot -->
  <Suspense>
    <div class="page-container">
      <AsyncHeader />
      <AsyncContent />
      <AsyncFooter />
    </div>

    <template #fallback>
      <div class="loading-container">
        <LoadingHeader />
        <LoadingContent />
      </div>
    </template>
  </Suspense>
</template>

Alternative: Dedicated Layout Component

<!-- PageLayout.vue - Contains all async components -->
<script setup>
import AsyncHeader from './AsyncHeader.vue';
import AsyncContent from './AsyncContent.vue';
import AsyncFooter from './AsyncFooter.vue';
</script>

<template>
  <div class="page-layout">
    <AsyncHeader />
    <AsyncContent />
    <AsyncFooter />
  </div>
</template>
<!-- Usage -->
<template>
  <Suspense>
    <PageLayout />
    <template #fallback>
      <SkeletonLayout />
    </template>
  </Suspense>
</template>

Key Points

  1. Always wrap multiple elements in a single container element
  2. This applies to both #default and #fallback slots
  3. Consider extracting complex layouts into dedicated components
  4. The single-child requirement helps Vue track the component tree correctly

References