Modul 3: Komponenten

Async Components & Suspense

Komponenten lazy laden und elegante Ladezustände mit Suspense verwalten.

Warum Async Components?

Nicht jede Komponente wird sofort gebraucht. Modale Dialoge, komplexe Editoren oder Dashboard-Widgets können bei Bedarf geladen werden. Das reduziert die initiale Bundle-Größe und beschleunigt den ersten Seitenaufruf.

🛤️

Rails-Vergleich: Turbo Frames & Lazy Loading

In Rails nutzt du <turbo-frame src="/comments" loading="lazy">, um Inhalte erst bei Sichtbarkeit nachzuladen. Vue's defineAsyncComponent und <Suspense> lösen dasselbe Problem — nur auf Komponenten-Ebene statt auf HTML-Fragment-Ebene.

defineAsyncComponent

Mit defineAsyncComponent definierst du eine Komponente, die erst geladen wird, wenn sie zum ersten Mal gerendert wird. Vue nutzt dafür dynamische import()-Aufrufe.

Einfache Variante
<script setup lang="ts">
// Einfach: Nur der Import-Pfad
const HeavyChart = defineAsyncComponent(
  () => import('./HeavyChart.vue')
)
</script>

<template>
  <!-- Wird erst geladen, wenn die Komponente gerendert wird -->
  <HeavyChart v-if="showChart" :data="chartData" />
</template>
Erweiterte Optionen
<script setup lang="ts">
const AdminPanel = defineAsyncComponent({
  // Die zu ladende Komponente
  loader: () => import('./AdminPanel.vue'),

  // Wird während des Ladens angezeigt
  loadingComponent: LoadingSpinner,

  // Verzögerung bevor Loading-Komponente erscheint (ms)
  delay: 200,

  // Wird bei Fehler angezeigt
  errorComponent: ErrorDisplay,

  // Timeout — danach gilt es als Fehler (ms)
  timeout: 10000,
})
</script>
💡

Nuxt Lazy-Prefix

In Nuxt kannst du Komponenten automatisch lazy laden, indem du Lazy vor den Namen setzt: <LazyHeavyChart /> statt <HeavyChart />. Nuxt erledigt den Rest.

Lade- und Fehlerzustände

Die erweiterte Syntax erlaubt es, Platzhalter-Komponenten für den Ladezustand und Fehlerfall zu definieren. So sehen Nutzer immer ein sinnvolles UI.

Mit Lade-/Fehlerkomponenten
<script setup lang="ts">
import LoadingSkeleton from './LoadingSkeleton.vue'
import ErrorFallback from './ErrorFallback.vue'

const DashboardWidget = defineAsyncComponent({
  loader: () => import('./DashboardWidget.vue'),
  loadingComponent: LoadingSkeleton,
  errorComponent: ErrorFallback,
  delay: 100,
  timeout: 5000,

  // Optional: bei Fehler eigene Logik
  onError(error, retry, fail, attempts) {
    if (attempts <= 3) {
      // Bis zu 3 Mal automatisch wiederholen
      retry()
    } else {
      fail()
    }
  },
})
</script>

Die Suspense-Komponente

<Suspense> ist Vues eingebaute Lösung, um auf asynchrone Abhängigkeiten zu warten. Sie rendert einen Fallback-Inhalt, bis alle Async-Operationen im #default-Slot abgeschlossen sind.

Suspense Grundlagen
<template>
  <Suspense>
    <!-- Hauptinhalt (wartet auf async setup) -->
    <template #default>
      <AsyncDashboard />
    </template>

    <!-- Fallback während des Ladens -->
    <template #fallback>
      <div class="loading">
        <Spinner />
        <p>Dashboard wird geladen...</p>
      </div>
    </template>
  </Suspense>
</template>

<script setup lang="ts">
// Events von Suspense
// @pending — wenn ein neuer async-Vorgang startet
// @resolve — wenn alle async-Vorgänge abgeschlossen sind
// @fallback — wenn der Fallback angezeigt wird
</script>
ℹ️

Wann wird Suspense ausgelöst?

Suspense wartet auf:

  • Komponenten mit async setup()
  • Top-Level await in <script setup>
  • Async-Komponenten im Default-Slot

Suspense + Data Fetching

In Nuxt wird <Suspense> oft mit useFetch oder useAsyncData kombiniert. Nuxt wraps Seiten automatisch in Suspense.

AsyncUserProfile.vue
<!-- AsyncUserProfile.vue -->
<script setup lang="ts">
// Top-Level await → löst Suspense aus
const { data: user } = await useFetch('/api/users/1')

// Mehrere async-Aufrufe parallel
const [posts, followers] = await Promise.all([
  useFetch('/api/users/1/posts'),
  useFetch('/api/users/1/followers'),
])
</script>

<template>
  <div class="profile">
    <h2>{{ user?.name }}</h2>
    <p>{{ posts.data.value?.length }} Posts</p>
    <p>{{ followers.data.value?.length }} Follower</p>
  </div>
</template>
Verwendung mit Suspense
<template>
  <Suspense>
    <AsyncUserProfile />

    <template #fallback>
      <div class="animate-pulse space-y-4">
        <div class="h-8 bg-gray-200 rounded w-1/3" />
        <div class="h-4 bg-gray-200 rounded w-2/3" />
        <div class="h-4 bg-gray-200 rounded w-1/2" />
      </div>
    </template>
  </Suspense>
</template>

<script setup lang="ts">
// Fehler abfangen
onErrorCaptured((error) => {
  console.error('Async-Fehler:', error)
  // return false → Fehler nicht weiter propagieren
})
</script>

Wann Async Components einsetzen?

Nicht jede Komponente sollte lazy geladen werden. Hier ist eine Orientierung:

✅ Gute Kandidaten

  • • Modale Dialoge & Drawer
  • • Tab-Inhalte (nur aktiver Tab geladen)
  • • Schwere Bibliotheken (Charts, Editoren)
  • • Admin-Bereiche / seltene Features
  • • Below-the-fold Inhalte

❌ Nicht ideal

  • • Kleine, häufig genutzte Komponenten
  • • Navigation / Header / Footer
  • • Komponenten im Above-the-fold Bereich
  • • Formular-Inputs und Buttons
  • • Alles unter 5 KB

Interaktive Demo

Simuliertes Async-Loading mit konfigurierbarer Verzögerung und Fehlerquote:

Suspense-Simulation
Interaktiv
<Suspense>

Klicke "Laden starten" um die Simulation zu beginnen.

loadState: "idle" · delay: 1500ms · result: "success"

Zusammenfassung

ℹ️

Kernpunkte

  • defineAsyncComponent lädt Komponenten erst bei Bedarf
  • In Nuxt reicht der Lazy-Prefix: <LazyModal />
  • <Suspense> rendert Fallback-UI während des Ladens
  • Kombiniere Suspense mit useFetch für Data-Fetching
  • Nur große / seltene Komponenten lazy laden — nicht alles
  • Immer Fehler- und Ladezustände definieren