Vue 3 Teleport und Suspense: Fortgeschrittene Muster und Interviewfragen 2026

Meistern Sie Vue 3 Teleport und Suspense mit fortgeschrittenen Mustern, dem defer-Prop und praxisnahen Interviewfragen für Senior Vue-Entwickler.

Vue 3 Teleport und Suspense: Fortgeschrittene Muster und Interviewfragen 2026

Vue 3 Teleport und Suspense lösen zwei unterschiedliche Rendering-Herausforderungen: Das Platzieren von Komponenten außerhalb ihrer DOM-Hierarchie und das Koordinieren asynchroner Abhängigkeiten. Beide sind eingebaute Komponenten, die keine Imports erfordern, und beide tauchen häufig in Interviews für Senior Vue-Entwickler auf.

Vue 3.5 Teleport Update

Vue 3.5 hat das defer-Prop zu Teleport hinzugefügt, das Ziele erlaubt, die später im Komponentenbaum gerendert werden. Dies löst das häufige SSR-Timing-Problem, bei dem das Portal-Ziel zum Zeitpunkt des Mountens nicht existiert.

Teleport Grundlagen und das defer-Prop

Teleport verschiebt gerenderten Inhalt an eine andere DOM-Position, während der Vue-Komponentenbaum erhalten bleibt. Die Komponente bleibt ein logisches Kind ihres Parents: Props, Events und provide/inject funktionieren normal. Vue DevTools zeigt die teleportierte Komponente unter ihrem Parent verschachtelt, nicht unter dem Zielelement.

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>

Das to-Prop akzeptiert einen CSS-Selektor-String oder eine DOM-Element-Referenz. Wenn das Ziel zum Zeitpunkt des Mountens nicht existiert, gibt Teleport eine Warnung aus und rendert nichts. Dies verursacht Probleme mit SSR und dynamischen Layouts.

Das defer-Prop (Vue 3.5+)

Das defer-Prop weist Teleport an, zu warten, bis andere Teile der Anwendung gemountet sind, bevor das Ziel aufgelöst wird. Dies ermöglicht das Teleportieren zu Elementen, die später in derselben Komponente oder in Geschwisterkomponenten gerendert werden.

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

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

Ohne defer schlägt dieser Code fehl, da #dynamic-target nicht existiert, wenn Teleport mountet. Mit defer löst Vue das Ziel auf, nachdem alle Komponenten im selben Tick gemountet wurden. Das Ziel muss dennoch im selben Mount- oder Update-Zyklus rendern.

Mehrere Teleports und bedingtes Rendering

Mehrere Teleport-Komponenten können dasselbe Element als Ziel haben. Inhalt wird in Deklarationsreihenfolge angehängt:

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

Das disabled-Prop steuert, ob Teleportation stattfindet. Wenn deaktiviert, wird Inhalt inline gerendert. Dieses Muster behandelt responsive Layouts, bei denen Modals auf Desktop überlagern, aber auf Mobile inline rendern sollen:

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 für asynchrone Komponenten-Orchestrierung

Suspense koordiniert asynchrone Abhängigkeiten über einen Komponentenbaum. Wenn eine Nachkommen-Komponente eine ungelöste asynchrone Abhängigkeit hat, zeigt Suspense den Fallback-Slot-Inhalt an. Sobald alle Abhängigkeiten aufgelöst sind, wechselt Suspense zum Default-Slot.

Experimenteller Status

Suspense bleibt in Vue 3.5 experimentell. Die API kann sich ändern, bevor sie den stabilen Status erreicht. Produktionseinsatz erfordert die Akzeptanz dieses Risikos.

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

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

Suspense verfolgt zwei Arten von asynchronen Abhängigkeiten: Komponenten mit async setup() und asynchrone Komponenten, die mit defineAsyncComponent erstellt wurden.

Async Setup mit Top-Level Await

Vue 3 script setup unterstützt Top-Level await. Jede Komponente, die Top-Level await verwendet, wird zu einer asynchronen Abhängigkeit, die Suspense verfolgen kann:

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>

Diese Komponente kann nicht rendern, bis der Fetch abgeschlossen ist. Eine übergeordnete Suspense-Boundary zeigt den Fallback bis zur Auflösung an. Ohne Suspense-Boundary rendert die Komponente einfach nicht.

Verschachteltes Suspense mit dem suspensible-Prop

Komplexe Anwendungen haben oft mehrere asynchrone Boundaries. Vue 3.3 führte das suspensible-Prop ein, um zu steuern, wie verschachtelte Suspense-Komponenten mit ihren Parents interagieren.

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>

Wenn suspensible gesetzt ist, registriert sich das innere Suspense bei der übergeordneten Suspense-Boundary. Das Parent wartet darauf, dass sowohl AsyncNavigation als auch AsyncContent aufgelöst werden, bevor es seinen Fallback versteckt. Das innere Suspense dient weiterhin als lokale Boundary: Wenn AsyncContent länger braucht, wird ContentSkeleton angezeigt, während AsyncNavigation sichtbar bleibt.

Ohne suspensible arbeitet das innere Suspense unabhängig. Das Parent zeigt seinen Fallback nur während AsyncNavigation lädt und ignoriert AsyncContent vollständig.

Bereit für deine Vue.js / Nuxt.js-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Kombination von Teleport, Suspense und Transitions

Reale Anwendungen kombinieren diese Muster. Ein Modal, das Inhalt asynchron lädt, profitiert von allen dreien:

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>

Die Verschachtelungsreihenfolge ist wichtig: Teleport umschließt Transition, die das bedingte Element umschließt, das Suspense enthält. Dies stellt sicher, dass die Transition das gesamte Modal einschließlich der Ladezustände animiert.

Fehlerbehandlung mit Suspense

Suspense behandelt keine Fehler. Fehlgeschlagene asynchrone Abhängigkeiten lehnen ihre Promises ab und propagieren Fehler den Komponentenbaum hinauf. Der onErrorCaptured Lifecycle-Hook fängt diese Fehler an der 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>

Dieses Muster umschließt jeden asynchronen Komponentenbaum mit sowohl Lade- als auch Fehlerzuständen. Der Retry-Button löscht den Fehler, was Vue veranlasst, neu zu rendern und die asynchrone Operation erneut zu versuchen.

Interviewfragen zu Teleport und Suspense

Senior Vue-Positionen testen diese Muster zunehmend. Häufige Fragen und was starke Antworten auszeichnet:

F: Wann sollte Teleport anstelle von CSS-Positionierung verwendet werden?

Schwache Antwort: "Wenn z-index nicht funktioniert."

Starke Antwort: Teleport löst DOM-Hierarchie-Probleme, die CSS nicht lösen kann. Ein Modal innerhalb eines Containers mit overflow: hidden oder transform erstellt einen neuen Stacking Context, was position: fixed bricht. Teleport verschiebt das Modal zu body und entkommt diesen Einschränkungen, während Vues logischer Komponentenbaum erhalten bleibt.

F: Wie unterscheidet sich Suspense von Ladezuständen in jeder Komponente?

Schwache Antwort: "Suspense ist sauberer."

Starke Antwort: Suspense koordiniert mehrere asynchrone Abhängigkeiten. Zehn Komponenten mit individuellen Ladezuständen zeigen zehn Spinner zu verschiedenen Zeiten. Suspense zeigt einen Spinner, bis alle zehn aufgelöst sind, und enthüllt sie dann zusammen. Dies schafft eine glattere wahrgenommene Performance und einfacheren Komponentencode, da asynchrone Komponenten ihre eigenen Ladezustände nicht verwalten müssen.

F: Was passiert, wenn ein Teleport-Ziel nicht existiert?

Der Inhalt wird nicht gerendert und Vue protokolliert eine Warnung. Das defer-Prop von Vue 3.5 adressiert dies, indem es auf das Mounten anderer Komponenten wartet. Ohne defer muss sichergestellt werden, dass Ziele in index.html oder in einer Parent-Komponente existieren, die vor den Children mountet.

F: Kann Suspense mit der Options API arbeiten?

Ja. Jede Komponente mit async setup() triggert Suspense, unabhängig davon, ob sie anderswo Composition API oder Options API verwendet. Die Setup-Funktion ist die asynchrone Boundary, nicht der Komponentenstil.

Teleport und Suspense in Nuxt 3

Nuxt 3 erweitert diese Muster mit SSR-Überlegungen. Die Nuxt-Dokumentation zum Rendering behandelt Hydration-Timing.

Für Teleport erfordert Nuxt, dass das Ziel vor der Hydration existiert. Portal-Ziele können in app.vue hinzugefügt oder das defer-Prop verwendet werden:

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

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

Für Suspense integrieren sich Nuxts useFetch und useAsyncData automatisch. Page-Komponenten, die diese Composables verwenden, arbeiten mit Nuxts eingebautem Suspense ohne manuelle Konfiguration.

Produktionsmuster für Vue 3.5

  • Teleport-Ziele in index.html: Stabile Portal-Ziele erstellen, die existieren, bevor Vue-Code ausgeführt wird. Dies vermeidet Timing-Probleme vollständig.
  • Granulare Suspense-Boundaries: Ein Suspense pro unabhängiger Ladeeinheit. Ein Dashboard mit separaten Widgets profitiert von separaten Suspense-Boundaries, sodass schnelle Widgets sofort erscheinen.
  • Error Boundaries über Suspense: Suspense immer mit Fehlerbehandlung umschließen. Netzwerkfehler und API-Fehler dürfen nicht den gesamten Baum zum Absturz bringen.
  • Transition-Timing: Bei der Kombination von Suspense mit Transition mode="out-in" setzen, um Überlappungen während Zustandsänderungen zu verhindern.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tägliche Challenge

Findest du den Bug in Vue.js / Nuxt.js?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 28. August 2026

Teilen

Verwandte Artikel