<Suspense> es un componente integrado que renderiza contenido de fallback mientras sus hijos asíncronos se resuelven. Funciona con dos tipos de dependencias asíncronas: componentes asíncronos (defineAsyncComponent) y componentes con un async setup().
Uso básico
<template>
<Suspense>
<!-- Slot por defecto: el contenido asíncrono -->
<AsyncDashboard />
<!-- Slot fallback: se muestra mientras carga -->
<template #fallback>
<LoadingSkeleton />
</template>
</Suspense>
</template>Suspense espera a que todas las dependencias asíncronas dentro del slot por defecto se resuelvan antes de cambiar del fallback al contenido real.
Async setup
Un componente con async setup() (usando await de nivel superior en <script setup>) es automáticamente una dependencia de Suspense:
<!-- UserProfile.vue -->
<script setup>
const user = await fetchUser() // await de nivel superior
const posts = await fetchPosts(user.id)
</script>
<template>
<h1>{{ user.name }}</h1>
<PostList :posts="posts" />
</template><!-- Parent.vue -->
<template>
<Suspense>
<UserProfile />
<template #fallback>Cargando perfil...</template>
</Suspense>
</template>Un único elemento raíz en cada slot
Suspense rastrea un único hijo inmediato por slot. Envuelve varios elementos:
<template>
<Suspense>
<div>
<AsyncHeader />
<AsyncContent />
</div>
<template #fallback>
<div>
<LoadingSpinner />
<p>Cargando...</p>
</div>
</template>
</Suspense>
</template>Timeout para re-activaciones
Cuando Suspense ya está resuelto y comienza nuevo trabajo asíncrono (por ejemplo, al cambiar de vista), el contenido anterior permanece visible hasta que expira un timeout. Configura timeout para controlar cuándo vuelve a aparecer el fallback:
<template>
<!-- Muestra el fallback tras 200ms si la nueva vista no ha resuelto -->
<Suspense :timeout="200">
<component :is="currentView" :key="currentView" />
<template #fallback>Cargando...</template>
</Suspense>
</template>Eventos de Suspense
Controla el estado de carga programáticamente con @pending, @resolve y @fallback:
<script setup>
import { ref } from 'vue'
const isLoading = ref(false)
</script>
<template>
<ProgressBar v-if="isLoading" />
<Suspense @pending="isLoading = true" @resolve="isLoading = false">
<AsyncPage />
<template #fallback><PageSkeleton /></template>
</Suspense>
</template>Anidamiento con RouterView, Transition y KeepAlive
El orden correcto de anidamiento es RouterView, luego Transition, luego KeepAlive y luego Suspense:
<template>
<RouterView v-slot="{ Component }">
<Transition mode="out-in">
<KeepAlive>
<Suspense>
<component :is="Component" />
<template #fallback>Cargando...</template>
</Suspense>
</KeepAlive>
</Transition>
</RouterView>
</template>Suspense sigue siendo experimental
A partir de Vue 3.5, Suspense funciona pero está marcado como experimental. La API podría cambiar en versiones futuras. En producción, mantén los límites Suspense al mínimo y documenta dónde los usas.