Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Enforce Vue 3 best practices—Composition API, script setup, TypeScript, component boundaries, and reactivity patterns
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/component-suspense.md
1---2title: Suspense Component Best Practices3impact: MEDIUM4impactDescription: Suspense coordinates async dependencies with fallback UI; misconfiguration leads to missing loading states or confusing UX5type: best-practice6tags: [vue3, suspense, async-components, async-setup, loading, fallback, router, transition, keepalive]7---89# Suspense Component Best Practices1011**Impact: MEDIUM** - `<Suspense>` coordinates async dependencies (async components or async setup) and renders a fallback while they resolve. Misconfiguration leads to missing loading states, empty renders, or subtle UX bugs.1213## Task List1415- Wrap default and fallback slot content in a single root node16- Use `timeout` when you need the fallback to appear on reverts17- Force root replacement with `:key` when you need Suspense to re-trigger18- Add `suspensible` to nested Suspense boundaries (Vue 3.3+)19- Use `@pending`, `@resolve`, and `@fallback` for programmatic loading state20- Nest `RouterView` -> `Transition` -> `KeepAlive` -> `Suspense` in that order21- Keep Suspense usage centralized and documented in production2223## Single Root in Default and Fallback Slots2425Suspense tracks a single immediate child in both slots. Wrap multiple elements in a single element or component.2627**BAD:**28```vue29<template>30<Suspense>31<AsyncHeader />32<AsyncList />3334<template #fallback>35<LoadingSpinner />36<LoadingHint />37</template>38</Suspense>39</template>40```4142**GOOD:**43```vue44<template>45<Suspense>46<div>47<AsyncHeader />48<AsyncList />49</div>5051<template #fallback>52<div>53<LoadingSpinner />54<LoadingHint />55</div>56</template>57</Suspense>58</template>59```6061## Fallback Timing on Reverts (`timeout`)6263When Suspense is already resolved and new async work starts, the previous content remains visible until the timeout elapses. Use `timeout="0"` for immediate fallback or a short delay to avoid flicker.6465**BAD:**66```vue67<template>68<Suspense>69<component :is="currentView" :key="viewKey" />7071<template #fallback>72Loading...73</template>74</Suspense>75</template>76```7778**GOOD:**79```vue80<template>81<Suspense :timeout="200">82<component :is="currentView" :key="viewKey" />8384<template #fallback>85Loading...86</template>87</Suspense>88</template>89```9091## Pending State Only Re-triggers on Root Replacement9293Once resolved, Suspense only re-enters pending when the root node of the default slot changes. If async work happens deeper in the tree, no fallback appears.9495**BAD:**96```vue97<template>98<Suspense>99<TabContainer>100<AsyncDashboard v-if="tab === 'dashboard'" />101<AsyncSettings v-else />102</TabContainer>103104<template #fallback>105Loading...106</template>107</Suspense>108</template>109```110111**GOOD:**112```vue113<template>114<Suspense>115<component :is="tabs[tab]" :key="tab" />116117<template #fallback>118Loading...119</template>120</Suspense>121</template>122```123124## Use `suspensible` for Nested Suspense (Vue 3.3+)125126Nested Suspense boundaries need `suspensible` on the inner boundary so the parent can coordinate loading state. Without it, inner async content may render empty nodes until resolved.127128**BAD:**129```vue130<template>131<Suspense>132<LayoutShell>133<Suspense>134<AsyncWidget />135<template #fallback>Loading widget...</template>136</Suspense>137</LayoutShell>138139<template #fallback>Loading layout...</template>140</Suspense>141</template>142```143144**GOOD:**145```vue146<template>147<Suspense>148<LayoutShell>149<Suspense suspensible>150<AsyncWidget />151<template #fallback>Loading widget...</template>152</Suspense>153</LayoutShell>154155<template #fallback>Loading layout...</template>156</Suspense>157</template>158```159160## Track Loading with Suspense Events161162Use `@pending`, `@resolve`, and `@fallback` for analytics, global loading indicators, or coordinating UI outside the Suspense boundary.163164```vue165<script setup>166import { ref } from 'vue'167168const isLoading = ref(false)169170const onPending = () => {171isLoading.value = true172}173174const onResolve = () => {175isLoading.value = false176}177</script>178179<template>180<LoadingBar v-if="isLoading" />181182<Suspense @pending="onPending" @resolve="onResolve">183<AsyncPage />184<template #fallback>185<PageSkeleton />186</template>187</Suspense>188</template>189```190191## Recommended Nesting with RouterView, Transition, KeepAlive192193When combining these components, the nesting order should be `RouterView` -> `Transition` -> `KeepAlive` -> `Suspense` so each wrapper works correctly.194195**BAD:**196```vue197<template>198<RouterView v-slot="{ Component }">199<Suspense>200<KeepAlive>201<Transition mode="out-in">202<component :is="Component" />203</Transition>204</KeepAlive>205</Suspense>206</RouterView>207</template>208```209210**GOOD:**211```vue212<template>213<RouterView v-slot="{ Component }">214<Transition mode="out-in">215<KeepAlive>216<Suspense>217<component :is="Component" />218<template #fallback>Loading...</template>219</Suspense>220</KeepAlive>221</Transition>222</RouterView>223</template>224```225226## Treat Suspense Cautiously in Production227228In production code, keep Suspense boundaries minimal, document where they are used, and have a fallback loading strategy if you ever need to replace or refactor them.229