Kurz erklärt
Lazy Loading (verzögertes Laden) ist eine Performance-Technik, bei der Ressourcen – insbesondere Bilder – erst dann vom Server angefordert werden, wenn sie tatsächlich benötigt werden, also wenn der Nutzer zu ihnen scrollt.
Ohne Lazy Loading: Alle 50 Bilder einer Seite werden sofort geladen, obwohl nur 3 im initialen Viewport sichtbar sind → langsame Ladezeit.
Mit Lazy Loading: Nur die 3 sichtbaren Bilder werden initial geladen, die restlichen 47 erst beim Scrollen → schnellere initiale Ladezeit.
Implementierung:
- HTML-Attribut:
loading="lazy" (native Browser-Unterstützung)
- JavaScript-Bibliotheken: für erweiterte Kontrolle (z. B. Intersection Observer API)
- Framework-spezifisch: Astro, Next.js haben Built-in-Support
Wichtig für Core Web Vitals:
- Verbessert LCP, wenn Above-the-Fold-Bilder NICHT lazy geladen werden
- Kann CLS verursachen, wenn Platzhalter-Dimensionen fehlen
- Best Practice: Hero-Bilder
eager laden, Rest lazy
Das loading="lazy"-Attribut wird von allen modernen Browsern unterstützt und ist die einfachste Implementierung.
Wie funktioniert Lazy Loading?
Native Browser-Implementierung (loading=“lazy”)
HTML:
<img src="image.jpg" loading="lazy" alt="Beschreibung" width="800" height="600">
Browser-Verhalten:
- Browser parst HTML und sieht
loading="lazy"
- Bild wird NICHT sofort geladen
- Browser berechnet, wann Bild im Viewport sichtbar sein wird
- Kurz bevor Nutzer zum Bild scrollt, wird es geladen
- Bild ist sichtbar, wenn Nutzer ankommt (kein Flackern)
Wann lädt der Browser?
- Chrome/Edge: ~1250px vor dem Viewport (bei schneller Verbindung)
- Firefox: ~200px vor dem Viewport
- Safari: ~500px vor dem Viewport
Distance-Threshold variiert je nach Verbindungsgeschwindigkeit (4G vs. 3G).
JavaScript-Implementierung (Intersection Observer)
Für erweiterte Kontrolle (z. B. andere Schwellenwerte):
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // Bild laden
img.removeAttribute('data-src');
observer.unobserve(img); // Beobachtung stoppen
}
});
}, {
rootMargin: '50px' // 50px vor Viewport laden
});
images.forEach(img => imageObserver.observe(img));
HTML:
<img data-src="image.jpg" src="placeholder.jpg" alt="Beschreibung">
Vorteil: Volle Kontrolle über Schwellenwerte, Platzhalter, Animationen.
Lazy Loading und Core Web Vitals
LCP (Largest Contentful Paint)
Problem: Lazy Loading verzögert LCP, wenn Hero-Bild lazy geladen wird.
Falsch:
<img src="hero.jpg" loading="lazy" alt="Hero">
→ Hero-Bild wird erst geladen, wenn im Viewport → LCP schlechter!
Richtig:
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">
→ Hero-Bild wird sofort geladen → LCP gut!
Best Practice:
- Above-the-Fold-Bilder (Hero, Logo):
loading="eager"
- Below-the-Fold-Bilder:
loading="lazy"
Siehe auch: LCP
CLS (Cumulative Layout Shift)
Problem: Bilder ohne Dimensionen verursachen Layout-Shift beim Laden.
Falsch:
<img src="image.jpg" loading="lazy" alt="Bild">
→ Browser kennt Bildhöhe nicht → Layout springt beim Laden!
Richtig:
<img src="image.jpg" loading="lazy" alt="Bild" width="800" height="600">
→ Browser reserviert Platz → kein Layout-Shift!
Noch besser (Aspect Ratio):
<img src="image.jpg" loading="lazy" alt="Bild" style="aspect-ratio: 16/9;">
Siehe auch: CLS
INP (Interaction to Next Paint)
Lazy Loading beeinflusst INP indirekt:
- Weniger Bilder → weniger Netzwerk-Requests → Browser weniger beschäftigt
- Schnellere initiale Ladezeit → schnellere Interaktivität
Was sollte lazy geladen werden?
✅ Lazy Loading geeignet
Bilder below-the-fold:
<img src="product-1.jpg" loading="lazy" width="400" height="300">
<img src="product-2.jpg" loading="lazy" width="400" height="300">
Videos:
<video src="demo.mp4" loading="lazy" controls></video>
Iframes (z. B. YouTube-Embeds):
<iframe src="https://www.youtube.com/embed/..." loading="lazy"></iframe>
Background-Images (CSS):
<div class="lazy-bg" data-bg="background.jpg"></div>
// Mit Intersection Observer laden
❌ NICHT lazy loaden
Hero-Bilder / Above-the-Fold:
<!-- Falsch -->
<img src="hero.jpg" loading="lazy">
<!-- Richtig -->
<img src="hero.jpg" loading="eager" fetchpriority="high">
Logos:
<img src="logo.svg" loading="eager" alt="Logo">
CSS-Dateien, JavaScript-Bundles:
- Lazy Loading gilt für
<img>, <iframe>, <video>
- Nicht für
<link> (CSS) oder <script> (außer mit async/defer)
Kritische Fonts:
<!-- Nicht lazy loaden -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
Framework-spezifische Implementierungen
Astro
Automatisches Lazy Loading für Bilder via <Image>-Komponente:
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
<!-- Eager loading für Hero -->
<Image src={heroImage} alt="Hero" loading="eager" />
<!-- Lazy loading für Rest -->
<Image src={productImage} alt="Produkt" loading="lazy" />
Astro optimiert automatisch:
- Responsive Images (srcset)
- Format-Conversion (WebP, AVIF)
- Dimensionen werden automatisch gesetzt (kein CLS)
Next.js
next/image-Komponente:
import Image from 'next/image';
// Eager loading für Hero
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // = loading="eager"
/>
// Lazy loading für Rest (Standard)
<Image
src="/product.jpg"
alt="Produkt"
width={400}
height={300}
// loading="lazy" ist Standard
/>
Next.js-Vorteile:
- Automatische Bildoptimierung
- Placeholder (blur, color)
- Responsive Images
React (ohne Framework)
Mit lazy()-Funktion für Code-Splitting:
import { lazy, Suspense } from 'react';
const ProductGallery = lazy(() => import('./ProductGallery'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ProductGallery />
</Suspense>
);
}
Für Bilder: Intersection Observer (siehe oben) oder Library wie react-lazy-load-image-component.
Browser-Support
loading=“lazy”
| Browser | Support seit |
|---|
| Chrome | Version 76 (2019) |
| Edge | Version 79 (2020) |
| Firefox | Version 75 (2020) |
| Safari | Version 15.4 (2022) |
| Opera | Version 63 (2019) |
Coverage: 95%+ aller Nutzer (Stand 2024)
Fallback: Browser ohne Support laden Bild sofort (graceful degradation).
Intersection Observer API
Support: 97%+ aller Nutzer
Polyfill verfügbar für alte Browser.
Vorher/Nachher-Vergleich
Metriken beobachten:
- Initial Load Time: Zeit bis alle Above-the-Fold-Ressourcen geladen
- Total Page Weight: Reduzierung um 50-70% initial
- LCP: Sollte sich NICHT verschlechtern (Hero eager laden!)
- Requests: Deutlich weniger initiale Requests
Tools:
- PageSpeed Insights
- Lighthouse
- WebPageTest
Beispiel-Ergebnis:
Ohne Lazy Loading:
- 50 Requests initial
- 3,5 MB Payload
- LCP: 2,8 s
Mit Lazy Loading:
- 10 Requests initial
- 800 KB Payload
- LCP: 2,3 s
Lazy Loading für CSS & JavaScript
JavaScript Code-Splitting
Ohne Code-Splitting:
// Eine große Bundle-Datei: 500 KB
import { everything } from './everything.js';
Mit Code-Splitting:
// Nur laden, wenn benötigt
button.addEventListener('click', async () => {
const { modal } = await import('./modal.js'); // 50 KB
modal.open();
});
Framework-Unterstützung:
- Webpack:
import() Syntax
- Vite:
import() Syntax
- Next.js:
next/dynamic
- Astro: automatisches Code-Splitting
CSS Code-Splitting
Critical CSS inline, Rest deferred:
<head>
<!-- Critical CSS inline -->
<style>
/* Above-the-Fold-Styles */
</style>
<!-- Non-critical CSS deferred -->
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
</head>
Tools für Critical CSS-Extraktion:
- Critical (npm package)
- Critters (Webpack/Vite Plugin)
- PurgeCSS
Lazy Loading Best Practices
1. Dimensionen immer angeben
<!-- Falsch: CLS-Risiko -->
<img src="image.jpg" loading="lazy" alt="Bild">
<!-- Richtig: Platz reserviert -->
<img src="image.jpg" loading="lazy" alt="Bild" width="800" height="600">
2. Hero-Bilder eager laden
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">
3. Responsive Images kombinieren
<img
srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
src="large.jpg"
loading="lazy"
alt="Responsive Image"
width="1200"
height="800"
>
4. Platzhalter für bessere UX
Blur-Placeholder:
<img
src="full-image.jpg"
loading="lazy"
style="background: url('tiny-blur-placeholder.jpg') no-repeat center; background-size: cover;"
alt="Bild"
>
Farb-Placeholder:
<img
src="image.jpg"
loading="lazy"
style="background-color: #f0f0f0;"
alt="Bild"
>
5. Lazy Loading + CDN kombinieren
<img
src="https://cdn.example.com/image.jpg"
loading="lazy"
alt="Bild"
width="800"
height="600"
>
CDN liefert Bilder schnell, Lazy Loading reduziert Anzahl initial geladener Bilder.
Siehe auch: CDN
Häufige Fehler
❌ Hero-Bild lazy laden: Verschlechtert LCP dramatisch
✅ Lösung: Hero-Bilder mit loading="eager" und fetchpriority="high"
❌ Keine Dimensionen angeben: Verursacht CLS
✅ Lösung: Immer width und height setzen
❌ Alle Bilder lazy laden: First 2-3 Bilder sollten eager sein
✅ Lösung: Above-the-Fold-Bilder eager, Rest lazy
❌ Schwellenwert zu klein: Bilder laden zu spät, Nutzer sieht Ladevorgang
✅ Lösung: Native loading="lazy" nutzt optimale Schwellenwerte
❌ Lazy Loading für kritisches CSS/JS: Blockiert Rendering
✅ Lösung: Nur für unkritische Ressourcen nutzen
Nächste Schritte
- Bilder identifizieren: Welche sind Above-the-Fold (eager), welche Below-the-Fold (lazy)?
loading="lazy" zu Below-the-Fold-Bildern hinzufügen
- Hero-Bild prüfen:
loading="eager" + fetchpriority="high"
- Dimensionen (
width, height) zu allen Bildern hinzufügen
- LCP vor/nach Lazy Loading messen (PageSpeed Insights)
- CLS prüfen: Gibt es Layout-Shifts? → Dimensionen korrigieren
- Für Astro/Next.js: Framework-spezifische Image-Komponenten nutzen
Siehe auch
LCP – Lazy Loading kann LCP verbessern oder verschlechtern
CLS – Dimensionen verhindern Layout-Shifts
Core Web Vitals – Gesamtbild der Performance-Metriken
CDN – Kombiniert mit Lazy Loading für optimale Performance