Lazy Loading und Code-Splitting richtig umsetzen 2026
Jede Website lädt Code, den der Nutzer im aktuellen Moment gar nicht braucht: das Skript für den Checkout, während er die Startseite liest, oder das Chat-Widget, das er nie anklickt. Dieser Ballast verzögert den Start. Laut Web Almanac sind im Median 44 Prozent (Web Almanac, 2024) des ausgelieferten JavaScripts beim Laden ungenutzt, das entspricht 206 KB (Web Almanac, 2024) totem Code allein auf Mobilgeräten. Lazy Loading und Code-Splitting drehen das Prinzip um: Es wird nur geladen, was für die erste interaktive Darstellung nötig ist, alles andere folgt bei Bedarf. Der Effekt ist direkt geschäftsrelevant -- eine um nur 0,1 Sekunden schnellere mobile Ladezeit steigerte Retail-Conversions um 8,4 Prozent (Google/Deloitte, 2020). Dieser Artikel zeigt, wie Sie Bild- und Komponenten-Lazy-Loading, routenbasiertes Splitting und dynamische Importe sauber umsetzen, ohne die Core Web Vitals zu verschlechtern.
Warum die initiale Payload über Erfolg entscheidet
Die Größe des initial geladenen JavaScripts bestimmt, wie schnell eine Seite interaktiv wird. JavaScript ist dabei teurer als jede andere Ressource: Es muss heruntergeladen, dekomprimiert, geparst, kompiliert und ausgeführt werden -- und jeder dieser Schritte blockiert den Main Thread. Während dieser Blockade reagiert die Seite auf keinen Klick und kein Tippen. Im Median laden Startseiten heute 664 KB (Web Almanac, 2025) JavaScript, was unkomprimiert oft das Zwei- bis Dreifache ergibt.
Die Geduld der Nutzer ist begrenzt. Google-Felddaten zeigen, dass die Wahrscheinlichkeit eines Absprungs um 32 Prozent (Google/SOASTA, 2017) steigt, wenn die Ladezeit von einer auf drei Sekunden wächst, und um 90 Prozent (Google/SOASTA, 2017), wenn sie von einer auf fünf Sekunden klettert. 53 Prozent (Google, 2018) der mobilen Seitenbesuche werden abgebrochen, wenn das Laden länger als drei Sekunden dauert. Da inzwischen rund 57 Prozent (Statista, 2024) der weltweiten E-Commerce-Umsätze auf Mobilgeräten entstehen, trifft eine schwere Payload genau dort, wo Umsatz gemacht wird.
Lazy Loading und Code-Splitting setzen an der Wurzel an. Statt die gesamte Anwendung vorzuladen, wird die Auslieferung in einen kritischen Pfad und nachgelagerte Einheiten zerlegt. Das senkt nicht nur die Bytes, sondern verkürzt vor allem die Verarbeitungszeit auf dem Main Thread -- und damit Total Blocking Time, Interaction to Next Paint und Largest Contentful Paint zugleich. Eine strukturierte Frontend-Optimierung beginnt deshalb fast immer mit der Frage: Was muss wirklich sofort geladen werden?
Lazy Loading und Code-Splitting -- der Unterschied
Bilder per nativem Lazy Loading entlasten
Bilder sind oft der größte Posten im Seitengewicht. Natives Lazy Loading verschiebt das Laden von Bildern außerhalb des sichtbaren Bereichs, bis der Nutzer in ihre Nähe scrollt. Das genügt in den meisten Fällen ein einziges Attribut: loading="lazy" am img- oder iframe-Element. Die Unterstützung ist breit -- alle modernen Browser ab Chrome 77, Firefox 75 und Safari 15.4 beherrschen das Verfahren, zusammen über 95 Prozent (web.dev, 2024) der globalen Browsernutzung. Es ist damit eine der ressourcenschonendsten Optimierungen überhaupt, weil sie ohne zusätzliches JavaScript auskommt.
Das LCP-Bild nicht lazy laden
Die Regel ist eindeutig: Bilder im ersten sichtbaren Bereich werden eager geladen, idealerweise mit fetchpriority="high" für das LCP-Bild. Alle Bilder unterhalb der Falz erhalten loading="lazy". Ergänzend sollten width und height gesetzt sein, damit der Browser den Platz reserviert und kein Layout-Sprung (Cumulative Layout Shift) entsteht. Wer bereits an Bildoptimierung mit modernen Formaten arbeitet, kombiniert beides: kleinere Dateien und verzögertes Laden ergänzen sich. Erfreulicherweise wächst die Adoption -- 34 Prozent (Web Almanac, 2024) der Websites nutzten 2024 natives Lazy Loading auf Mobilgeräten, gegenüber 27 Prozent ein Jahr zuvor.
Routenbasiertes Code-Splitting als Fundament
Die wirkungsvollste Form des Code-Splittings ist das routenbasierte Splitting. Jede Seite oder Route erhält ein eigenes Bundle, das erst geladen wird, wenn der Nutzer die Route tatsächlich besucht. Wer auf der Startseite landet, lädt nicht den Code für Produktdetailseite, Warenkorb und Konto-Verwaltung mit. Moderne Frameworks wie SvelteKit, Next.js und Nuxt erzeugen diese Aufteilung automatisch entlang der Routen-Struktur -- die initiale Payload sinkt dadurch oft erheblich, ohne dass Entwickler einzelne Importe von Hand verwalten müssen.
Bei serverseitig gerenderten Shops auf Basis von Shopware CE muss das Splitting bewusster konfiguriert werden, weil das Storefront-Bundle historisch monolithisch gewachsen ist. Hier hilft es, Plugin-Funktionalitäten in separate Entry Points zu zerlegen und selten genutzte Bereiche -- etwa die Wunschliste oder den Produktvergleich -- in eigene Chunks auszulagern. Die Details dazu vertieft unser Beitrag zur Shopware-Performance. Entscheidend ist, dass der kritische Pfad (Layout, Navigation, erstes Rendering) klein bleibt.
| Ansatz | Was wird geladen | Geeignet für | Typischer Effekt |
|---|---|---|---|
| Monolithisches Bundle | Gesamter App-Code sofort | Sehr kleine Sites | Hohe Blocking Time |
| Routenbasiertes Splitting | Nur Code der aktuellen Route | Multi-Page-Apps, Shops | Deutlich kleineres Initial-JS |
| Komponenten-Splitting | Komponente bei Bedarf | Schwere Widgets, Modals | Entlastet einzelne Routen |
| Vendor-Splitting | Bibliotheken in eigenem Chunk | Stabile Abhängigkeiten | Bessere Cache-Trefferquote |
Ein oft unterschätzter Nebeneffekt: Routenbasierte Chunks verbessern das Caching. Aendert sich nur der Code einer einzelnen Seite, muss der Browser auch nur diesen Chunk neu laden -- der Rest bleibt im Cache. Dasselbe gilt für Vendor-Chunks mit stabilen Bibliotheken: Sie ändern sich selten und bleiben über Deployments hinweg gültig. Dieser Caching-Vorteil wirkt besonders stark in Kombination mit CDN- und Edge-Caching, das die Chunks zusätzlich näher an den Nutzer bringt.
Dynamische Importe gezielt einsetzen
Während routenbasiertes Splitting meist automatisch geschieht, sind dynamische Importe das Werkzeug für feingranulare Kontrolle. Die Syntax import() gibt ein Promise zurück und weist den Bundler an, den importierten Code in einen separaten Chunk zu legen, der erst zur Laufzeit geladen wird. So lässt sich schwere Funktionalität exakt an den Moment koppeln, in dem der Nutzer sie braucht -- etwa ein Diagramm-Renderer, der erst beim Aufruf des Dashboards lädt, oder ein Karten-Widget, das erst beim Klick auf den Standort erscheint.
// Statischer Import: landet immer im initialen Bundle
// import { Chart } from './chart';
// Dynamischer Import: eigener Chunk, erst bei Bedarf geladen
button.addEventListener('click', async () => {
const { Chart } = await import('./chart');
new Chart(container).render(data);
});Der häufigste Anwendungsfall sind Interaktionen, die nicht jeder Nutzer auslöst: ein Chat-Widget, das erst bei Klick lädt, ein Bewertungsformular, das erst beim Scrollen in den Sichtbereich initialisiert wird, oder ein aufwendiger Editor, der nur auf bestimmten Seiten erscheint. Da im Median 22 Drittanbieter-Skripte (Web Almanac, 2024) pro Seite geladen werden, ist gerade hier das Einsparpotenzial groß. Wie sich externe Skripte gezielt entschlacken lassen, behandelt unser Beitrag zu Third-Party-Scripts und Performance.
Praxistipp: Ladezustand und Fehlerfall einplanen
Tree Shaking: ungenutzten Code automatisch entfernen
Code-Splitting verteilt Code, Tree Shaking entfernt ihn. Bei der Build-Optimierung analysiert der Bundler die Import-Kette und schließt alle Exporte aus, die nirgends verwendet werden. Das ist deshalb so wirksam, weil ein großer Teil der ausgelieferten Bytes ohnehin tot ist: Coverage-Daten des Web Almanac zeigen, dass die Median-Website 45 Prozent (Web Almanac, 2024) ungenutztes JavaScript ausliefert. Bemerkenswert ist die Herkunft -- 82,7 Prozent (Web Almanac, 2024) der verschwendeten Bytes stammen aus eigenem Code, nicht von Dritten. Das Aufräumen liegt also weitgehend in eigener Hand.
Damit Tree Shaking greift, müssen Abhängigkeiten im ES-Module-Format (import/export) vorliegen. CommonJS-Module mit require lassen sich nicht zuverlässig entfernen, weil ihre Exporte erst zur Laufzeit feststehen. Beim Auswählen von npm-Paketen lohnt deshalb der Blick in die package.json: Ein module- oder exports-Feld signalisiert ES-Module. Ein zweiter Stolperstein ist die sideEffects-Markierung. Module, die beim Import absichtlich Seiteneffekte auslösen -- etwa CSS injizieren -- dürfen nicht entfernt werden; eine falsche Markierung ist erfahrungsgemäß (Projekterfahrung) einer der häufigsten Gründe, warum Tree Shaking weniger einspart als erwartet.
ES-Module bevorzugen
Wählen Sie Bibliotheken mit nativem import/export. Nur dann kann der Bundler ungenutzte Teile sicher entfernen. CommonJS verhindert effektives Tree Shaking.
Granular importieren
Importieren Sie einzelne Funktionen statt ganzer Pakete. Ein pauschaler Default-Import zieht oft die komplette Bibliothek ins Bundle, auch wenn nur eine Funktion genutzt wird.
Bundle analysieren
Eine Treemap-Analyse zeigt, welche Module den meisten Platz belegen. Duplikate, überdimensionierte Importe und unnötige Polyfills werden so sofort sichtbar.
Prefetching: Lazy Loading ohne spürbare Wartezeit
Der berechtigte Einwand gegen aggressives Lazy Loading lautet: Verzögert das Nachladen nicht die Interaktion? Genau hier setzt Prefetching an. Der Browser lädt Chunks, die der Nutzer voraussichtlich gleich braucht, im Hintergrund vor -- mit niedriger Priorität, ohne die aktuelle Seite zu stören. Hovert ein Nutzer über einen Link, kann der Code der Zielseite bereits geladen werden, sodass der Wechsel sofort wirkt. Die Direktive und die Intersection Observer API ermöglichen dieses vorausschauende Laden.
Wichtig ist die Abstufung der Ressourcen-Hinweise. preload gilt für Ressourcen, die auf der aktuellen Seite zwingend gebraucht werden (etwa eine kritische Schriftart). prefetch gilt für wahrscheinliche nächste Schritte. Ein Zuviel an Prefetching kann jedoch Bandbreite verschwenden und schwächere Verbindungen belasten -- gerade mobil. Die Kunst liegt darin, nur das vorzuladen, was mit hoher Wahrscheinlichkeit gebraucht wird. Viele Frameworks lösen das elegant, indem sie Links im Viewport automatisch und nur über WLAN prefetchen.
Lazy Loading ohne Prefetching verschiebt das Problem nur in den Moment der Interaktion. Erst die Kombination aus bedarfsgesteuertem Laden und vorausschauendem Vorladen ergibt eine Erfahrung, die sich gleichzeitig schnell startet und schnell anfühlt.
Typische Fehler und wie Sie sie vermeiden
Lazy Loading und Code-Splitting sind mächtig, aber fehleranfällig, wenn sie schematisch angewendet werden. Der gravierendste Fehler ist das Lazy-Laden des LCP-Bilds oder anderer kritischer Inhalte -- das verschlechtert genau die Metrik, die man verbessern wollte. Ebenso problematisch ist Over-Splitting: Zerlegt man den Code in Dutzende winziger Chunks, summieren sich Anfrage-Overhead und Latenz, und die Gesamtleistung leidet. Sinnvolle Chunk-Größen liegen erfahrungsgemäß (Projekterfahrung) im Bereich weniger Dutzend Kilobytes.
- LCP-Bild und Above-the-Fold-Inhalte eager laden, nicht mit loading=lazy versehen
- width und height an Bildern setzen, um Layout-Shifts (CLS) zu vermeiden
- Chunks nicht übersplitten -- Anfrage-Overhead gegen Einsparung abwägen
- Dynamisch geladene Bereiche mit Skeleton-Platzhalter und Fehlerbehandlung absichern
- Wahrscheinliche nächste Routen gezielt prefetchen, statt alles vorzuladen
- ES-Module bevorzugen und sideEffects in package.json korrekt markieren
Ein weiterer Klassiker ist die fehlende Messung. Wer splittet, ohne vorher und nachher zu messen, optimiert im Blindflug. Wir empfehlen, die initiale JavaScript-Größe, Total Blocking Time und Largest Contentful Paint sowohl im Labor als auch im Feld zu beobachten. Eine fundierte Performance-Analyse deckt auf, welche Chunks wirklich relevant sind, wo ungenutzter Code steckt und ob Drittskripte die eigenen Optimierungen wieder zunichtemachen. Ohne diese Datengrundlage bleibt jede Splitting-Strategie Spekulation.
Messen, absichern und dauerhaft schlank bleiben
Eine schlanke Payload ist kein Dauerzustand, sondern eine Disziplin. Jedes neue Feature, jede Abhängigkeit und jedes Drittskript kann die initiale JavaScript-Größe wieder anwachsen lassen. Deshalb gehören Größen-Limits in die CI/CD-Pipeline: Build-Werkzeuge wie Vite und webpack können den Build mit einem Hinweis versehen oder abbrechen, sobald ein Chunk eine definierte Grenze überschreitet. So werden Performance-Regressionen sichtbar, bevor sie in Produktion gelangen.
Wir kombinieren synthetische Messungen unter kontrollierten Bedingungen mit Real User Monitoring aus echten Sitzungen. Synthetische Tests erkennen Regressionen schnell und reproduzierbar; Felddaten zeigen die tatsächliche Erfahrung auf unterschiedlichen Geräten und Verbindungen. Besonders aufschlussreich ist die Segmentierung nach Gerätetyp -- Mittelklasse-Smartphones erleben die JavaScript-Last erfahrungsgemäß (Projekterfahrung) deutlich stärker als Desktop-Geräte. Diese kontinuierliche Beobachtung ist Teil unserer Performance-Leistungen.
Der Kern in einem Satz
Der Aufwand zahlt sich mehrfach aus. Kleinere initiale Bundles verbessern Largest Contentful Paint und Total Blocking Time, was direkt auf die Core Web Vitals einzahlt und die organische Sichtbarkeit stärkt. Schnellere Interaktivität senkt die Absprungrate und hebt die Conversion. Und ein etabliertes Performance-Budget bewahrt das Ergebnis über Releases hinweg. So wird aus einer einmaligen Optimierung eine dauerhaft schnelle Website -- der Hebel, den eine spezialisierte Frontend-Optimierung konsequent bedient.