Vue 3 Teleport et Suspense : Patterns Avancés et Questions d'Entretien 2026
Maîtrisez Teleport et Suspense dans Vue 3.5 avec des patterns de production, la gestion des erreurs et les questions d'entretien technique les plus fréquentes.

Vue 3 Teleport et Suspense répondent à deux défis de rendu distincts : placer des composants en dehors de leur hiérarchie DOM et coordonner les dépendances asynchrones. Ces deux composants natifs ne nécessitent aucune importation et apparaissent fréquemment dans les entretiens pour développeurs Vue seniors.
Vue 3.5 a introduit la prop defer pour Teleport, permettant de cibler des éléments rendus plus tard dans l'arbre des composants. Cette fonctionnalité résout le problème courant de timing SSR où la cible du portail n'existe pas au moment du montage.
Fondamentaux de Teleport et la Prop defer
Teleport déplace le contenu rendu vers un emplacement DOM différent tout en préservant l'arbre des composants Vue. Le composant reste un enfant logique de son parent : les props, événements et provide/inject fonctionnent normalement. Vue DevTools affiche le composant téléporté sous son parent, pas sous l'élément cible.
<!-- ModalTrigger.vue -->
<script setup>
import { ref } from 'vue'
const isOpen = ref(false)
</script>
<template>
<button @click="isOpen = true">Open Modal</button>
<Teleport to="#modal-root">
<div v-if="isOpen" class="modal-overlay" @click.self="isOpen = false">
<div class="modal-content">
<slot />
<button @click="isOpen = false">Close</button>
</div>
</div>
</Teleport>
</template>La prop to accepte une chaîne de sélecteur CSS ou une référence d'élément DOM. Lorsque la cible n'existe pas au moment du montage, Teleport génère un avertissement et ne rend rien. Cela pose des problèmes avec le SSR et les layouts dynamiques.
La Prop defer (Vue 3.5+)
La prop defer indique à Teleport d'attendre que d'autres parties de l'application soient montées avant de résoudre la cible. Cela permet de téléporter vers des éléments rendus plus tard dans le même composant ou dans des composants frères.
<!-- App.vue -->
<template>
<Teleport defer to="#dynamic-target">
<NotificationBanner />
</Teleport>
<!-- Target rendered after the Teleport -->
<div id="dynamic-target"></div>
</template>Sans defer, ce code échoue car #dynamic-target n'existe pas lorsque Teleport monte. Avec defer, Vue résout la cible après que tous les composants du même tick aient été montés. La cible doit toujours être rendue dans le même cycle de montage ou de mise à jour.
Teleports Multiples et Rendu Conditionnel
Plusieurs composants Teleport peuvent cibler le même élément. Le contenu s'ajoute dans l'ordre de déclaration :
<template>
<Teleport to="#notifications">
<Toast message="First" />
</Teleport>
<Teleport to="#notifications">
<Toast message="Second" />
</Teleport>
</template>
<!-- Result in #notifications:
<div>First</div>
<div>Second</div>
-->La prop disabled contrôle si la téléportation a lieu. Lorsqu'elle est désactivée, le contenu est rendu sur place. Ce pattern gère les layouts responsifs où les modales doivent se superposer sur desktop mais s'afficher en ligne sur mobile :
<script setup>
import { useMediaQuery } from '@vueuse/core'
const isMobile = useMediaQuery('(max-width: 768px)')
</script>
<template>
<Teleport to="body" :disabled="isMobile">
<MobileDrawer />
</Teleport>
</template>Suspense pour l'Orchestration des Composants Asynchrones
Suspense coordonne les dépendances asynchrones à travers un arbre de composants. Lorsqu'un composant descendant a une dépendance asynchrone non résolue, Suspense affiche le contenu du slot fallback. Une fois toutes les dépendances résolues, Suspense bascule vers le slot par défaut.
Suspense reste expérimental dans Vue 3.5. L'API peut changer avant d'atteindre un statut stable. L'utilisation en production nécessite d'accepter ce risque.
<!-- Dashboard.vue -->
<template>
<Suspense>
<template #default>
<DashboardContent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>Suspense suit deux types de dépendances asynchrones : les composants avec async setup() et les composants asynchrones créés avec defineAsyncComponent.
Setup Asynchrone avec Top-Level Await
Vue 3 script setup supporte le top-level await. Tout composant utilisant top-level await devient une dépendance asynchrone que Suspense peut suivre :
<!-- UserProfile.vue -->
<script setup>
const response = await fetch('/api/user/profile')
const user = await response.json()
</script>
<template>
<div class="profile">
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>Ce composant ne peut pas être rendu tant que le fetch n'est pas terminé. Un boundary Suspense parent affiche le fallback jusqu'à la résolution. Sans boundary Suspense, le composant ne se rend simplement pas.
Suspense Imbriqués avec la Prop suspensible
Les applications complexes ont souvent plusieurs boundaries asynchrones. Vue 3.3 a introduit la prop suspensible pour contrôler comment les composants Suspense imbriqués interagissent avec leurs parents.
<!-- PageLayout.vue -->
<template>
<Suspense>
<template #default>
<header>
<AsyncNavigation />
</header>
<main>
<!-- Inner Suspense with its own fallback -->
<Suspense suspensible>
<template #default>
<AsyncContent />
</template>
<template #fallback>
<ContentSkeleton />
</template>
</Suspense>
</main>
</template>
<template #fallback>
<PageSkeleton />
</template>
</Suspense>
</template>Lorsque suspensible est défini, le Suspense interne s'enregistre auprès du boundary Suspense parent. Le parent attend que AsyncNavigation et AsyncContent soient tous deux résolus avant de masquer son fallback. Le Suspense interne sert toujours de boundary local : si AsyncContent prend plus de temps, ContentSkeleton s'affiche pendant que AsyncNavigation reste visible.
Sans suspensible, le Suspense interne opère indépendamment. Le parent affiche son fallback uniquement pendant le chargement de AsyncNavigation, ignorant complètement AsyncContent.
Prêt à réussir tes entretiens Vue.js / Nuxt.js ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Combinaison de Teleport, Suspense et Transitions
Les applications réelles combinent ces patterns. Une modale qui charge du contenu de manière asynchrone bénéficie des trois :
<!-- AsyncModal.vue -->
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const isOpen = ref(false)
const AsyncModalContent = defineAsyncComponent({
loader: () => import('./ModalContent.vue'),
loadingComponent: () => import('./ModalSkeleton.vue'),
delay: 200,
timeout: 10000
})
</script>
<template>
<button @click="isOpen = true">Open</button>
<Teleport to="body">
<Transition name="modal">
<div v-if="isOpen" class="modal-wrapper">
<Suspense>
<template #default>
<AsyncModalContent @close="isOpen = false" />
</template>
<template #fallback>
<ModalSkeleton />
</template>
</Suspense>
</div>
</Transition>
</Teleport>
</template>
<style>
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
</style>L'ordre d'imbrication est important : Teleport enveloppe Transition, qui enveloppe l'élément conditionnel, qui contient Suspense. Cela garantit que la transition anime l'ensemble de la modale, y compris les états de chargement.
Gestion des Erreurs avec Suspense
Suspense ne gère pas les erreurs. Les dépendances asynchrones échouées rejettent leurs promesses et propagent les erreurs dans l'arbre des composants. Le hook de cycle de vie onErrorCaptured capture ces erreurs au niveau du boundary :
<!-- ErrorBoundary.vue -->
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false // Prevent propagation
})
</script>
<template>
<div v-if="error" class="error-state">
<p>Failed to load: {{ error.message }}</p>
<button @click="error = null">Retry</button>
</div>
<Suspense v-else>
<template #default>
<slot />
</template>
<template #fallback>
<slot name="loading" />
</template>
</Suspense>
</template>Ce pattern enveloppe tout arbre de composants asynchrones avec à la fois des états de chargement et d'erreur. Le bouton de réessai efface l'erreur, causant la re-tentative de l'opération asynchrone par Vue.
Questions d'Entretien sur Teleport et Suspense
Les postes Vue seniors testent de plus en plus ces patterns. Questions courantes et ce qui distingue les bonnes réponses :
Q : Quand utiliser Teleport plutôt que le positionnement CSS ?
Réponse faible : « Quand z-index ne fonctionne pas. »
Réponse forte : Teleport résout les problèmes de hiérarchie DOM que CSS ne peut pas résoudre. Une modale à l'intérieur d'un conteneur avec overflow: hidden ou transform crée un nouveau contexte d'empilement, cassant position: fixed. Teleport déplace la modale vers body, échappant à ces contraintes tout en préservant l'arbre logique des composants Vue.
Q : Comment Suspense diffère-t-il des états de chargement dans chaque composant ?
Réponse faible : « Suspense est plus propre. »
Réponse forte : Suspense coordonne plusieurs dépendances asynchrones. Dix composants avec des états de chargement individuels affichent dix spinners à des moments différents. Suspense affiche un seul spinner jusqu'à ce que les dix soient résolus, puis les révèle ensemble. Cela crée une performance perçue plus fluide et un code de composant plus simple puisque les composants asynchrones n'ont pas besoin de gérer leurs propres états de chargement.
Q : Que se passe-t-il quand une cible Teleport n'existe pas ?
Le contenu n'est pas rendu et Vue affiche un avertissement. La prop defer de Vue 3.5 résout ce problème en attendant que d'autres composants montent d'abord. Sans defer, il faut s'assurer que les cibles existent dans index.html ou dans un composant parent qui monte avant les enfants.
Q : Suspense fonctionne-t-il avec l'Options API ?
Oui. Tout composant avec async setup() déclenche Suspense, qu'il utilise la Composition API ou l'Options API ailleurs. La fonction setup est la frontière asynchrone, pas le style du composant.
Teleport et Suspense dans Nuxt 3
Nuxt 3 étend ces patterns avec des considérations SSR. La documentation Nuxt sur le SSR couvre le timing d'hydratation.
Pour Teleport, Nuxt exige que la cible existe avant l'hydratation. Il faut ajouter les cibles de portail dans app.vue ou utiliser la prop defer :
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<!-- Portal targets for SSR -->
<div id="modal-root"></div>
<div id="toast-root"></div>
</template>Pour Suspense, useFetch et useAsyncData de Nuxt s'intègrent automatiquement. Les composants de page utilisant ces composables fonctionnent avec le Suspense intégré de Nuxt sans configuration manuelle. Consultez le module Vue composables de SharpSkill pour une préparation aux entretiens connexe.
Patterns de Production pour Vue 3.5
- Cibles Teleport dans index.html : Créer des cibles de portail stables qui existent avant l'exécution de tout code Vue. Cela évite entièrement les problèmes de timing.
- Boundaries Suspense granulaires : Un Suspense par unité de chargement indépendante. Un dashboard avec des widgets séparés bénéficie de boundaries Suspense séparés pour que les widgets rapides apparaissent immédiatement.
- Boundaries d'erreur au-dessus de Suspense : Toujours envelopper Suspense avec une gestion d'erreur. Les échecs réseau et erreurs API ne doivent pas faire planter l'arbre entier.
- Timing des transitions : Lors de la combinaison de Suspense avec Transition, définir
mode="out-in"pour éviter les chevauchements lors des changements d'état.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tu saurais repérer le bug en Vue.js / Nuxt.js ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 28 août 2026
Tags
Partager
Articles similaires

Vue 3 Script Setup et defineModel en 2026 : Syntaxe Moderne et Questions d'Entretien
Guide complet sur la syntaxe script setup de Vue 3 et defineModel. Exploration des patterns modernes, TypeScript, et préparation aux entretiens techniques Vue.js.

Vue 3 Reactivity Transform en 2026 : $ref, $computed et Questions d'Entretien
Guide complet sur le Reactivity Transform de Vue 3, incluant $ref, $computed, les macros de réactivité et les questions d'entretien technique fréquentes.

Vue 3 avec TypeScript en 2026 : props, emits et composables typés
Les patterns Vue 3 et TypeScript en 2026 : defineProps, defineEmits, composables, defineModel et provide/inject typés, avec exemples concrets et questions d'entretien.