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.
Inhaltsverzeichnis
- 1. Was Lazy Loading in Vue Apps konkret bedeutet
- 2. Routen lazy laden mit Vue Router
- 3. Komponenten lazy laden mit defineAsyncComponent
- 4. Prefetching und Preloading gezielt steuern
- 5. Lazy Loading von Bildern und Medien
- 6. Intersection Observer für sichtbarkeitsbasiertes Laden
- 7. Ladezustände, Skeletons und Suspense
- 8. Häufige Fehler beim Lazy Loading
- 9. Lazy Loading Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.