Lazy Loading von Routen und Komponenten in Vue
AI generated
<v/>
{ }
Vue.js · Vue Router · Lazy Loading · Performance
Lazy Loading von Routen und Komponenten
Inhalte erst laden, wenn sie wirklich gebraucht werden

Lazy Loading verschiebt das Laden von Routen, Komponenten, Bildern und Daten auf den Moment, in dem ein Nutzer sie tatsächlich sieht oder anfordert. Mit Vue Router, defineAsyncComponent, Intersection Observer und Suspense lässt sich dieses Prinzip konsequent durch die gesamte Anwendung ziehen.

18 Min. Lesezeit Vue Router · defineAsyncComponent · Intersection Observer Vue 3.4+ · Vue Router 4

1. Was Lazy Loading in Vue Apps konkret bedeutet

Lazy Loading verschiebt das Laden einer Ressource, sei es eine Route, eine Komponente, ein Bild oder ein Datensatz, vom Zeitpunkt des ersten Seitenaufrufs auf den Zeitpunkt, an dem sie tatsächlich benötigt wird. Der Gegensatz dazu ist Eager Loading, bei dem alles sofort geladen wird, unabhängig davon, ob es der Nutzer in der aktuellen Sitzung überhaupt sieht. In Vue Apps betrifft Lazy Loading mehrere Ebenen gleichzeitig: die Router Ebene, die Komponenten Ebene und die Medien Ebene, und jede dieser Ebenen braucht ein eigenes technisches Werkzeug.

Der Nutzen von Lazy Loading zeigt sich am deutlichsten bei Anwendungen mit vielen Routen oder langen, bilderreichen Seiten. Ein Nutzer, der die Startseite einer Vue App öffnet, muss nicht den Code für die Einstellungsseite, den Adminbereich oder ein selten genutztes Exportformular mitladen. Ebenso muss ein Bild, das erst nach mehreren Bildschirmhöhen sichtbar wird, nicht sofort heruntergeladen werden, wenn der Nutzer möglicherweise nie so weit scrollt. Lazy Loading reduziert so die initiale Datenmenge und beschleunigt den Zeitpunkt, an dem die Seite interaktiv wird.

Wichtig ist die Abgrenzung zu Bundle Splitting: Bundle Splitting ist die Build Zeit Technik, die überhaupt erst getrennte Dateien erzeugt. Lazy Loading ist die Laufzeit Entscheidung, wann genau diese getrennten Dateien angefordert werden. Beide Konzepte ergänzen sich, aber Lazy Loading braucht zusätzlich eine bewusste Steuerung darüber, welcher Trigger das Nachladen auslöst, etwa eine Navigation, eine Sichtbarkeit im Viewport oder eine Nutzerinteraktion.

2. Routen lazy laden mit Vue Router

Der Router ist der naheliegendste Ort für Lazy Loading, weil jede Route ohnehin eine natürliche Grenze für benötigten Code darstellt. Vue Router unterstützt dynamische Imports als Component Definition direkt, ohne zusätzliche Konfiguration: Statt eine Komponente am Dateikopf zu importieren, übergibt man eine Funktion, die ein Promise mit der Komponente zurückgibt. Beim ersten Navigieren zu dieser Route lädt der Browser den zugehörigen Chunk nach, bei jeder weiteren Navigation liegt er bereits im Cache.

Für verschachtelte Routen mit vielen Unterrouten lohnt sich eine konsistente Struktur: Jede Top Level Route und jede signifikante Unterroute wird lazy geladen, während kleine, eng verwandte Unterrouten, die ohnehin fast immer gemeinsam besucht werden, im selben Chunk verbleiben können. Diese Entscheidung trifft man am besten nach einer kurzen Analyse des tatsächlichen Navigationsverhaltens der Nutzer, nicht rein nach Dateistruktur.


// router/index.js — lazy loading every top level route
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  { path: '/', component: () => import('../views/Home.vue') },
  { path: '/products', component: () => import('../views/Products.vue') },
  {
    path: '/admin',
    // Nested routes: only the parent is fetched first,
    // children are fetched when their specific path is visited
    children: [
      { path: 'users', component: () => import('../views/admin/Users.vue') },
      { path: 'settings', component: () => import('../views/admin/Settings.vue') }
    ]
  }
]

export const router = createRouter({
  history: createWebHistory(),
  routes
})

3. Komponenten lazy laden mit defineAsyncComponent

Innerhalb einer bereits geladenen Route gibt es häufig Komponenten, die nicht sofort sichtbar sind: Ein Modal, ein Tab Inhalt, der erst nach einem Klick angezeigt wird, oder ein komplexer Editor, der nur in einem bestimmten Bearbeitungsmodus erscheint. defineAsyncComponent lädt genau diese Komponenten erst beim ersten Rendering Versuch, nicht beim Rendern der umgebenden Route. Für Lazy Loading auf Komponentenebene ist das die wichtigste API und funktioniert unabhängig vom Router.

Ein praktisches Muster: Tab basierte Oberflächen, bei denen jeder Tab Inhalt eine eigene, potenziell schwere Komponente ist. Nur der aktive Tab wird tatsächlich gerendert, alle anderen Tabs bleiben als asynchrone Komponenten ungeladen, bis der Nutzer sie tatsächlich anklickt. Kombiniert mit v-if statt v-show verhindert man zusätzlich, dass inaktive Tabs überhaupt im DOM existieren, was den Speicherverbrauch weiter senkt.


// components/TabbedSettings.vue — each tab is loaded on first activation
import { defineAsyncComponent, ref } from 'vue'

const GeneralTab = defineAsyncComponent(() => import('./tabs/GeneralTab.vue'))
const BillingTab = defineAsyncComponent(() => import('./tabs/BillingTab.vue'))
const SecurityTab = defineAsyncComponent(() => import('./tabs/SecurityTab.vue'))

const activeTab = ref('general')
const tabs = { general: GeneralTab, billing: BillingTab, security: SecurityTab }

// v-if in the template ensures inactive tabs never mount,
// so their async component never even starts loading

4. Prefetching und Preloading gezielt steuern

Reines Lazy Loading hat einen Nachteil: Der Nutzer wartet auf den Chunk, sobald er tatsächlich navigiert. Prefetching schließt diese Lücke, indem Chunks bereits geladen werden, bevor sie gebraucht werden, aber nachdem die kritischen Ressourcen für die aktuelle Ansicht bereits geladen sind. Vite fügt für jeden dynamischen Import automatisch <link rel="modulepreload"> Hints für dessen direkte Abhängigkeiten hinzu, was bereits eine Grundform von Preloading darstellt.

Für gezieltes Prefetching auf Nutzerinteraktion lohnt sich ein Muster, das den Import bereits bei mouseenter auf einen Link auslöst, lange bevor der eigentliche Klick erfolgt. Bei durchschnittlichen Reaktionszeiten zwischen Hover und Klick von mehreren hundert Millisekunden ist der Chunk in vielen Fällen bereits vollständig geladen, wenn der Klick tatsächlich erfolgt, wodurch sich Lazy Loading und wahrgenommene Geschwindigkeit nicht ausschließen, sondern ergänzen.


// composables/usePrefetchOnHover.js
export function usePrefetchOnHover(importFn) {
  let prefetched = false
  return {
    onMouseenter: () => {
      if (!prefetched) {
        prefetched = true
        importFn() // starts the chunk download ahead of the actual click
      }
    }
  }
}

// Usage in a component
// const { onMouseenter } = usePrefetchOnHover(() => import('../views/Checkout.vue'))
// <router-link to="/checkout" @mouseenter="onMouseenter">Checkout</router-link>

5. Lazy Loading von Bildern und Medien

Bilder machen in vielen Vue Apps den größten Anteil der übertragenen Datenmenge aus, weshalb Lazy Loading hier oft einen größeren Effekt hat als bei JavaScript Chunks. Der native Weg ist das loading="lazy" Attribut auf <img> Tags, das der Browser selbst auswertet, ohne dass JavaScript nötig ist. Für Vue Templates bedeutet das lediglich, das Attribut konsequent auf alle Bilder unterhalb des sichtbaren Bereichs zu setzen, während Bilder im initial sichtbaren Bereich, etwa ein Hero Bild, bewusst ohne loading="lazy" bleiben, damit sie nicht künstlich verzögert werden.

Für Videos und schwere eingebettete Inhalte wie Karten oder Drittanbieter Widgets reicht das native Attribut nicht aus. Hier kombiniert man Lazy Loading typischerweise mit einer bedingten Rendering Strategie: Ein Platzhalter Element wird gerendert, das eigentliche Video Element oder Iframe erst nach einer Nutzerinteraktion oder nach Sichtbarkeit im Viewport eingefügt. Das verhindert, dass ein YouTube Iframe bereits beim Laden der Seite mehrere hundert Kilobyte an zusätzlichen Skripten nachlädt, obwohl der Nutzer das Video vielleicht nie abspielt.


<!-- Native lazy loading for below-the-fold images -->
<template>
  <img
    v-for="product in products"
    :key="product.id"
    :src="product.thumbnail"
    :alt="product.name"
    loading="lazy"
    decoding="async"
  />

  <!-- Hero image stays eager — it is visible immediately -->
  <img :src="heroImage" alt="Hero" loading="eager" fetchpriority="high" />
</template>

6. Intersection Observer für sichtbarkeitsbasiertes Laden

Für Komponenten, die nicht über das native loading="lazy" Attribut abgedeckt sind, etwa ganze Widget Bereiche, Kommentar Sektionen oder Empfehlungslisten weiter unten auf der Seite, ist die Intersection Observer API das passende Werkzeug. Ein Composable registriert einen Observer auf einem Platzhalter Element und lädt die eigentliche Komponente erst, wenn dieses Element in den sichtbaren Bereich scrollt. Der Vorteil gegenüber Scroll Event Listenern: Intersection Observer läuft nicht im Haupt Thread bei jedem Scroll Event, sondern wird vom Browser effizient und asynchron ausgewertet.

Diese Technik lässt sich elegant als wiederverwendbares Composable kapseln, das mit jeder beliebigen asynchronen Komponente kombiniert werden kann. Für Lazy Loading in Vue Apps ist das die Ergänzung zu defineAsyncComponent: Während defineAsyncComponent das Laden an das Rendering koppelt, koppelt der Intersection Observer das Rendering selbst an die Sichtbarkeit, was die tatsächliche Ladezeit noch weiter nach hinten verschiebt, bis der Nutzer wirklich in die Nähe des Inhalts scrollt.


// composables/useLazyMount.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useLazyMount() {
  const target = ref(null)
  const isVisible = ref(false)
  let observer

  onMounted(() => {
    observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        isVisible.value = true
        observer.disconnect() // only trigger once
      }
    }, { rootMargin: '200px' }) // start loading slightly before it enters view

    if (target.value) observer.observe(target.value)
  })

  onUnmounted(() => observer?.disconnect())

  return { target, isVisible }
}

// Template usage:
// <div ref="target"><RecommendationsWidget v-if="isVisible" /></div>

7. Ladezustände, Skeletons und Suspense

Lazy Loading ohne sichtbares Feedback wirkt für Nutzer wie eine kaputte Anwendung: Ein Klick auf einen Tab oder eine Navigation zeigt für einen Moment nichts an, bevor der Inhalt erscheint. Vue's <Suspense> Komponente löst dieses Problem, indem sie einen Fallback Zustand rendert, während eine asynchrone Komponente oder ein async setup() noch lädt. Für Lazy Loading in größeren Anwendungen ist Suspense die zentrale Stelle, um konsistente Ladezustände über die gesamte Anwendung hinweg zu definieren, statt in jeder einzelnen Komponente eigene Ladelogik zu bauen.

Skeleton Screens, die die ungefähre Form des kommenden Inhalts andeuten, wirken für Nutzer schneller als ein simpler Spinner, selbst bei identischer tatsächlicher Ladezeit. Die Kombination aus Suspense für den strukturellen Ladezustand und einem thematisch passenden Skeleton als Fallback Komponente ist das robusteste Muster für Lazy Loading Feedback in Vue Apps, weil es sowohl für Routen als auch für einzelne asynchrone Komponenten funktioniert.


// App.vue — Suspense wraps lazily loaded route content

<template>
  <router-view v-slot="{ Component }">
    <Suspense timeout="0">
      <template #default>
        <component :is="Component" />
      </template>
      <template #fallback>
        <ProductListSkeleton />
      </template>
    </Suspense>
  </router-view>
</template>

8. Häufige Fehler beim Lazy Loading

Der häufigste Fehler ist Lazy Loading für Inhalte oberhalb des ersten Viewports, das sogenannte Above the Fold Element. Ein Hero Bild oder die primäre Navigationskomponente mit loading="lazy" zu versehen verzögert genau den Inhalt, der zuerst sichtbar sein soll, und verschlechtert dadurch Largest Contentful Paint statt es zu verbessern. Lazy Loading gehört ausschließlich zu Inhalten, die beim ersten Rendering nicht sichtbar sind.

Ein zweiter Fehler betrifft fehlendes Fallback Handling: Eine asynchrone Komponente ohne definierten Fehlerzustand hinterlässt bei einem Netzwerkfehler eine leere, verwirrende Lücke in der Oberfläche. defineAsyncComponent sollte praktisch immer mit errorComponent und einem sinnvollen timeout konfiguriert werden. Ein dritter Fehler: Intersection Observer Instanzen, die nicht mit disconnect() aufgeräumt werden, wenn die Komponente entfernt wird, was zu Memory Leaks führt, besonders in Single Page Applications mit häufigen Routenwechseln.

9. Lazy Loading Strategien im Vergleich

Je nach Art der Ressource passt eine andere Lazy Loading Technik am besten. Die folgende Übersicht ordnet die wichtigsten Ansätze nach Trigger und typischem Einsatzbereich.

Ressource Technik Auslöser Fallback nötig
Route Vue Router dynamic import Navigation Ja, Suspense oder Ladebalken
Komponente defineAsyncComponent Erstes Rendering Ja, loadingComponent
Bild loading="lazy" Nähe zum Viewport Nein, nativ gehandhabt
Widget Bereich Intersection Observer Sichtbarkeit im Viewport Optional, Platzhalter sinnvoll
Wahrscheinlich nächste Route Prefetch on hover Mouseenter auf Link Nein, läuft im Hintergrund

In der Praxis kombiniert man mehrere dieser Techniken innerhalb einer einzigen Vue App: Route Level Lazy Loading als Basis, Component Level Lazy Loading für schwere UI Bereiche innerhalb einer Route, native Bild Attribute für alles unterhalb des ersten Viewports, und Intersection Observer für die verbleibenden Sonderfälle wie eingebettete Drittanbieter Widgets.

Mironsoft

Lazy Loading und Ladezeit Optimierung für Vue Anwendungen

Zu viel Code und zu viele Bilder beim ersten Aufruf?

Wir bauen konsistentes Lazy Loading für Routen, Komponenten und Medien in eure Vue App ein, inklusive Prefetching, Skeleton Ladezuständen und sauberem Fehler Handling.

Route & Komponenten

Konsistentes Lazy Loading mit Vue Router und defineAsyncComponent

Medien Optimierung

Bilder, Videos und Widgets sichtbarkeitsbasiert nachladen

Ladezustände

Suspense und Skeleton Screens für wahrgenommene Geschwindigkeit

10. Zusammenfassung

Lazy Loading von Routen und Komponenten ist keine einzelne Technik, sondern ein Zusammenspiel mehrerer Werkzeuge auf unterschiedlichen Ebenen. Vue Router lädt Routen bei Navigation nach, defineAsyncComponent lädt schwere Komponenten erst bei ihrem ersten Rendering, native loading="lazy" Attribute verschieben Bilder unterhalb des Viewports, und Intersection Observer deckt alle übrigen Fälle wie Widget Bereiche ab. Prefetching auf Hover ergänzt reines Lazy Loading, damit wahrgenommene Geschwindigkeit nicht unter der verzögerten Ladung leidet.

Der wichtigste Grundsatz bleibt: Lazy Loading gehört nur zu Inhalten, die nicht sofort sichtbar sein müssen. Above the Fold Inhalte, allen voran Hero Bilder und primäre Navigation, sollten bewusst von jeder Lazy Loading Technik ausgenommen werden. Mit Suspense und Skeleton Screens als konsistentem Feedback Mechanismus lässt sich Lazy Loading so einsetzen, dass Nutzer eine schnelle, reaktionsfreudige Anwendung wahrnehmen, obwohl im Hintergrund kontinuierlich Code und Daten nachgeladen werden.

Lazy Loading in Vue Apps — Das Wichtigste auf einen Blick

Routen

Dynamic Imports in Vue Router laden jede Route erst bei Navigation, kombiniert mit Suspense für Ladezustände.

Komponenten

defineAsyncComponent für Modals, Tabs und schwere UI Bereiche, die nicht sofort sichtbar sind.

Medien

loading="lazy" für Bilder unterhalb des Viewports, niemals für Above the Fold Inhalte.

Feedback

Prefetch on Hover und Skeleton Screens sorgen für wahrgenommene Geschwindigkeit trotz Lazy Loading.

11. FAQ: Lazy Loading von Routen und Komponenten

1Lazy Loading vs. Bundle Splitting?
Bundle Splitting erzeugt Dateien zur Build Zeit, Lazy Loading entscheidet zur Laufzeit, wann sie angefordert werden.
2Jede Route lazy laden?
In den meisten Fällen ja, außer bei sehr kleinen Anwendungen mit wenigen Routen.
3Verzögerung bei Routen vermeiden?
Suspense mit Skeleton Fallback und Prefetching bei Hover, damit der Chunk oft schon vorliegt.
4defineAsyncComponent statt Router Level?
Für schwere, nicht sofort sichtbare Komponenten innerhalb einer bereits geladenen Route, etwa Modals.
5Warum kein lazy Hero Bild?
Es ist sofort sichtbar, Lazy Loading würde Largest Contentful Paint verschlechtern.
6Wofür Intersection Observer?
Für Widget Bereiche und Drittanbieter Komponenten, die loading=lazy nicht unterstützen.
7Netzwerkfehler beim Lazy Loading?
Ohne errorComponent bleibt die Stelle leer. defineAsyncComponent immer mit errorComponent und timeout konfigurieren.
8Lohnt sich Prefetching immer?
Bei vorhersehbaren Navigationspfaden fast immer, bei sehr verzweigten Anwendungen weniger.
9Observer manuell aufräumen?
Ja, mit disconnect() in onUnmounted, sonst entstehen Memory Leaks.
10Verbessert es Core Web Vitals?
Ja, vor allem Largest Contentful Paint und Time to Interactive profitieren von weniger initial geladenem Code.