Vue 3 Teleport en Suspense: Geavanceerde Patronen en Sollicitatievragen 2026

Beheers Vue 3 Teleport en Suspense met geavanceerde patronen, de defer prop en praktische sollicitatievragen voor senior Vue-ontwikkelaars.

Vue 3 Teleport en Suspense: Geavanceerde Patronen en Sollicitatievragen 2026

Vue 3 Teleport en Suspense lossen twee verschillende rendering-uitdagingen op: componenten plaatsen buiten hun DOM-hiërarchie en asynchrone afhankelijkheden coördineren. Beide zijn ingebouwde componenten die geen imports vereisen, en beide komen frequent voor in sollicitatiegesprekken voor senior Vue-ontwikkelaars.

Vue 3.5 Teleport Update

Vue 3.5 heeft de defer prop toegevoegd aan Teleport, waardoor targets die later in de componentboom worden gerenderd mogelijk zijn. Dit lost het veelvoorkomende SSR-timingprobleem op waarbij het portal-target niet bestaat op het moment van mounten.

Teleport Fundamenten en de defer Prop

Teleport verplaatst gerenderde content naar een andere DOM-locatie terwijl de Vue componentboom behouden blijft. De component blijft een logisch kind van zijn parent: props, events en provide/inject werken normaal. Vue DevTools toont de geteleporteerde component genest onder zijn parent, niet onder het target-element.

vue
<!-- 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>

De to prop accepteert een CSS-selector string of een DOM-element referentie. Wanneer het target niet bestaat op het moment van mounten, geeft Teleport een waarschuwing en rendert niets. Dit veroorzaakt problemen met SSR en dynamische layouts.

De defer Prop (Vue 3.5+)

De defer prop instrueert Teleport om te wachten tot andere delen van de applicatie zijn gemount voordat het target wordt opgelost. Dit maakt het mogelijk te teleporteren naar elementen die later worden gerenderd in dezelfde component of in sibling-componenten.

vue
<!-- App.vue -->
<template>
  <Teleport defer to="#dynamic-target">
    <NotificationBanner />
  </Teleport>

  <!-- Target rendered after the Teleport -->
  <div id="dynamic-target"></div>
</template>

Zonder defer faalt deze code omdat #dynamic-target niet bestaat wanneer Teleport mount. Met defer lost Vue het target op nadat alle componenten in dezelfde tick zijn gemount. Het target moet nog steeds renderen in dezelfde mount- of update-cyclus.

Meerdere Teleports en Conditionele Rendering

Meerdere Teleport-componenten kunnen hetzelfde element als target hebben. Content wordt toegevoegd in declaratievolgorde:

vue
<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>
-->

De disabled prop bepaalt of teleportatie plaatsvindt. Wanneer uitgeschakeld, rendert content inline. Dit patroon behandelt responsive layouts waar modals moeten overlappen op desktop maar inline moeten renderen op mobiel:

vue
<script setup>
import { useMediaQuery } from '@vueuse/core'

const isMobile = useMediaQuery('(max-width: 768px)')
</script>

<template>
  <Teleport to="body" :disabled="isMobile">
    <MobileDrawer />
  </Teleport>
</template>

Suspense voor Asynchrone Component-Orkestratie

Suspense coördineert asynchrone afhankelijkheden door een componentboom. Wanneer een afstammende component een onopgeloste asynchrone afhankelijkheid heeft, toont Suspense de fallback slot-content. Zodra alle afhankelijkheden zijn opgelost, schakelt Suspense over naar de default slot.

Experimentele Status

Suspense blijft experimenteel in Vue 3.5. De API kan veranderen voordat het een stabiele status bereikt. Productiegebruik vereist acceptatie van dit risico.

vue
<!-- Dashboard.vue -->
<template>
  <Suspense>
    <template #default>
      <DashboardContent />
    </template>

    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

Suspense volgt twee types asynchrone afhankelijkheden: componenten met async setup() en asynchrone componenten gemaakt met defineAsyncComponent.

Async Setup met Top-Level Await

Vue 3 script setup ondersteunt top-level await. Elke component die top-level await gebruikt, wordt een asynchrone afhankelijkheid die Suspense kan volgen:

vue
<!-- 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>

Deze component kan niet renderen totdat de fetch is voltooid. Een parent Suspense-boundary toont de fallback tot de resolutie. Zonder Suspense-boundary rendert de component simpelweg niet.

Geneste Suspense met de suspensible Prop

Complexe applicaties hebben vaak meerdere asynchrone boundaries. Vue 3.3 introduceerde de suspensible prop om te bepalen hoe geneste Suspense-componenten interacteren met hun parents.

vue
<!-- 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>

Wanneer suspensible is ingesteld, registreert de inner Suspense zich bij de parent Suspense-boundary. De parent wacht tot zowel AsyncNavigation als AsyncContent zijn opgelost voordat zijn fallback wordt verborgen. De inner Suspense dient nog steeds als lokale boundary: als AsyncContent langer duurt, wordt ContentSkeleton getoond terwijl AsyncNavigation zichtbaar blijft.

Zonder suspensible werkt de inner Suspense onafhankelijk. De parent toont zijn fallback alleen tijdens het laden van AsyncNavigation, waarbij AsyncContent volledig wordt genegeerd.

Klaar om je Vue.js / Nuxt.js gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Teleport, Suspense en Transitions Combineren

Echte applicaties combineren deze patronen. Een modal die content asynchroon laadt, profiteert van alle drie:

vue
<!-- 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>

De nestingvolgorde is belangrijk: Teleport omhult Transition, die het conditionele element omhult, dat Suspense bevat. Dit zorgt ervoor dat de transitie de gehele modal animeert inclusief laadstates.

Foutafhandeling met Suspense

Suspense handelt geen fouten af. Gefaalde asynchrone afhankelijkheden verwerpen hun promises en propageren fouten omhoog door de componentboom. De onErrorCaptured lifecycle hook vangt deze fouten op de boundary:

vue
<!-- 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>

Dit patroon omhult elke asynchrone componentboom met zowel laad- als foutstates. De retry-knop wist de fout, waardoor Vue opnieuw rendert en de asynchrone operatie opnieuw probeert.

Sollicitatievragen over Teleport en Suspense

Senior Vue-posities testen deze patronen steeds vaker. Veelvoorkomende vragen en wat sterke antwoorden onderscheidt:

V: Wanneer moet Teleport worden gebruikt in plaats van CSS-positionering?

Zwak antwoord: "Wanneer z-index niet werkt."

Sterk antwoord: Teleport lost DOM-hiërarchieproblemen op die CSS niet kan oplossen. Een modal binnen een container met overflow: hidden of transform creëert een nieuwe stacking context, wat position: fixed breekt. Teleport verplaatst de modal naar body, ontsnapt aan deze beperkingen terwijl Vue's logische componentboom behouden blijft.

V: Hoe verschilt Suspense van laadstates in elke component?

Zwak antwoord: "Suspense is schoner."

Sterk antwoord: Suspense coördineert meerdere asynchrone afhankelijkheden. Tien componenten met individuele laadstates tonen tien spinners op verschillende momenten. Suspense toont één spinner totdat alle tien zijn opgelost, en onthult ze dan samen. Dit creëert een soepelere waargenomen performance en eenvoudigere componentcode omdat asynchrone componenten hun eigen laadstates niet hoeven te beheren.

V: Wat gebeurt er wanneer een Teleport-target niet bestaat?

De content rendert niet en Vue logt een waarschuwing. Vue 3.5's defer prop adresseert dit door te wachten tot andere componenten eerst mounten. Zonder defer moeten targets bestaan in index.html of in een parent-component die mount vóór children.

V: Kan Suspense werken met de Options API?

Ja. Elke component met async setup() triggert Suspense, ongeacht of het elders Composition API of Options API gebruikt. De setup-functie is de asynchrone boundary, niet de componentstijl.

Teleport en Suspense in Nuxt 3

Nuxt 3 breidt deze patronen uit met SSR-overwegingen. De Nuxt-documentatie over rendering behandelt hydration-timing.

Voor Teleport vereist Nuxt dat het target bestaat vóór hydration. Portal-targets kunnen worden toegevoegd aan app.vue of de defer prop kan worden gebruikt:

vue
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>

  <!-- Portal targets for SSR -->
  <div id="modal-root"></div>
  <div id="toast-root"></div>
</template>

Voor Suspense integreren Nuxt's useFetch en useAsyncData automatisch. Page-componenten die deze composables gebruiken, werken met Nuxt's ingebouwde Suspense zonder handmatige configuratie.

Productiepatronen voor Vue 3.5

  • Teleport-targets in index.html: Creëer stabiele portal-targets die bestaan voordat Vue-code draait. Dit voorkomt timingproblemen volledig.
  • Granulaire Suspense-boundaries: Eén Suspense per onafhankelijke laadeenheid. Een dashboard met aparte widgets profiteert van aparte Suspense-boundaries zodat snelle widgets direct verschijnen.
  • Error Boundaries boven Suspense: Omhul Suspense altijd met foutafhandeling. Netwerkfouten en API-fouten mogen niet de hele boom crashen.
  • Transition-timing: Bij het combineren van Suspense met Transition, stel mode="out-in" in om overlap tijdens statuswijzigingen te voorkomen.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in Vue.js / Nuxt.js?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 28 augustus 2026

Delen

Gerelateerde artikelen