Wie gelingt Lazy Loading von Bildern in React ohne LCP-Nachteile?
Um ein Bild in React verzögert zu laden, rendern Sie ein normales Element
img mit loading="lazy". Belassen Sie die tatsächliche
URL in src, ergänzen Sie srcSet und
sizes, wenn responsive Varianten verfügbar sind, und geben Sie
width und height an, damit der Browser Platz
reservieren kann.
Die wichtige Ausnahme ist das Bild, das wahrscheinlich zum Largest-Contentful-Paint-Element (LCP) der Seite wird. Laden Sie dieses Bild ohne Lazy Loading und machen Sie es im initialen HTML auffindbar. Lazy Loading verzögert hier die Anfrage, die für die Seite besonders schnell abgeschlossen werden muss.
| Bildrolle | Ladeverhalten | Weiteres wichtiges Markup |
|---|---|---|
| Hero-Bild oder wahrscheinliches LCP-Bild | Sofort | fetchPriority="high" nach Überprüfung |
| Anderes Bild im ersten sichtbaren Bereich | Sofort | Abmessungen und responsive Quellen |
| Normales Inhaltsbild außerhalb des sichtbaren Bereichs | Verzögert | src, srcSet, sizes und Abmessungen |
| Dekorativer CSS-Hintergrund | Eigener Mechanismus | Inhaltlich relevante Elemente img nicht ersetzen |
„Above the fold“ entspricht keiner festen Anzahl von Bildern. Die Höhe des sichtbaren Bereichs, responsive Layouts, Banner und lokalisierte Inhalte können dasselbe Bild in den initial sichtbaren Bereich hinein oder daraus heraus verschieben. Ordnen Sie Bilder anhand der gerenderten Seite ein und überprüfen Sie die Entscheidung anschließend auf repräsentativen Bildschirmgrößen.
Die native Loading-Prop von React verwenden
React reicht die Prop loading an das native Bildelement des Browsers weiter.
Für ein normales Bild außerhalb des sichtbaren Bereichs benötigen Sie keinen Effekt, keinen
Scroll-Handler und keine Intersection-Observer-Bibliothek.
Die folgende lokale Vorschau rendert React auf dem Server und sendet vollständiges Bild-Markup an den Browser. Sie können die Komponenten in einer bestehenden React-Anwendung mit serverseitigem Rendering wiederverwenden. Die Vorschau benötigt Node.js und separat installiertes Corepack. Verwenden Sie eine gepflegte Node.js-Version. Diese Vorschau wurde mit Node.js 24.15.0 und 26.8.1, Yarn 4.12.0, React 19.2.6 und Chrome 151 getestet. Das sind getestete Versionen, keine grundsätzlichen Mindestversionen für das native Laden von Bildern. Die Befehle verwenden eine POSIX-Shell.
Erstellen Sie ein neues, leeres Verzeichnis react-image-preview und öffnen Sie dort ein
Terminal. Speichern Sie package.json:
{
"name": "react-image-preview",
"private": true,
"type": "module",
"packageManager": "yarn@4.12.0",
"dependencies": {
"react": "19.2.6",
"react-dom": "19.2.6"
},
"devDependencies": {
"esbuild": "0.27.0"
}
}
Speichern Sie .yarnrc.yml, um lokale Abhängigkeiten auszuwählen und die
Yarn-Programmdatei eines übergeordneten Projekts zu ignorieren:
nodeLinker: node-modules
enableGlobalCache: false
ignorePath: true
Erstellen Sie vor der Installation die Lockdatei als Projektgrenze, damit Yarn dieses Verzeichnis als eigenes Projekt behandelt:
touch yarn.lock && corepack yarn install
Falls die Installation fehlschlägt, beheben Sie das gemeldete Netzwerk- oder Paketmanagerproblem und wiederholen Sie den Befehl im selben Verzeichnis. Behalten Sie Ihre Quelldateien bei.
Erstellen Sie ein Verzeichnis images mit diesen vier WebP-Dateien aus Ihren
eigenen Fotos. Jedes Paar muss denselben Bildausschnitt zeigen. Die Breiten in
srcSet müssen den tatsächlichen Pixelbreiten der Dateien entsprechen.
Ändern Sie den Alternativtext so, dass er Ihre Fotos beschreibt.
| Datei | Pixelmaße | Rolle |
|---|---|---|
images/harbor-640.webp | 640×360 | Kleine Hero-Variante |
images/harbor-1280.webp | 1280×720 | Große Hero-Variante und Fallback |
images/workshop-480.webp | 480×320 | Kleine Inhaltsbild-Variante |
images/workshop-960.webp | 960×640 | Große Inhaltsbild-Variante und Fallback |
Speichern Sie diese Komponente als ResponsiveImage.tsx:
import type { ReactNode } from 'react'
interface ResponsiveImageProps {
alt: string
height: number
sizes: string
src: string
srcSet: string
width: number
}
export function ResponsiveImage({
alt,
height,
sizes,
src,
srcSet,
width,
}: ResponsiveImageProps): ReactNode {
return (
<img
className="responsive-image"
src={src}
srcSet={srcSet}
sizes={sizes}
width={width}
height={height}
loading="lazy"
decoding="async"
alt={alt}
/>
)
}
Belassen Sie eine tatsächliche Bild-URL in src. Wenn Sie die einzige URL
in ein benutzerdefiniertes Attribut data-src verschieben, hängt die Anfrage
von Ihrem JavaScript ab. Außerdem kann der Preload-Scanner des Browsers die URL dann nicht im
normalen Bild-Markup entdecken. Schlägt das Skript fehl, bleibt das Bild ebenfalls ungeladen.
Die Prop decoding="async" ist ein separater Hinweis zum Decodieren. Sie bestimmt
nicht, wann die Netzwerkanfrage beginnt, und ersetzt loading="lazy" nicht.
Speichern Sie ImagePage.tsx. Der große Abstandshalter platziert das Werkstattfoto
für dieses Ladeexperiment weit außerhalb des initial sichtbaren Bereichs. Dieses Layout ist nicht
zur Übernahme in Ihre Anwendung gedacht.
import type { ReactNode } from 'react'
import { HeroImage } from './HeroImage.tsx'
import { ResponsiveImage } from './ResponsiveImage.tsx'
export function ImagePage(): ReactNode {
return (
<main>
<h1>React image preview</h1>
<section className="hero-column">
<HeroImage />
<p>The harbor photo loads with the page.</p>
</section>
<section className="demo-spacer">
<p>Scroll to the workshop photo.</p>
</section>
<section className="image-column">
<ResponsiveImage
src="/images/workshop-960.webp"
srcSet="/images/workshop-480.webp 480w, /images/workshop-960.webp 960w"
sizes="(max-width: 40rem) 100vw, 40rem"
width={960}
height={640}
alt="A technician calibrating a camera rig"
/>
<p>The workshop photo keeps this caption in place while it loads.</p>
</section>
</main>
)
}
Lazy Loading ändert nur, wann die Anfrage beginnt. Es ändert weder die Bildgröße noch komprimiert, konvertiert, cached oder beschreibt es das Bild.
Das LCP-Bild ohne Lazy Loading laden
Wenden Sie loading="lazy" nicht auf ein wahrscheinliches LCP-Bild an. Rendern Sie
dessen URL im initialen HTML, wenn Ihr React-Framework serverseitiges Rendering unterstützt.
So kann der Browser sie entdecken, ohne auf clientseitiges Rendering oder eine weitere
Datenanfrage zu warten.
Speichern Sie HeroImage.tsx:
import type { ReactNode } from 'react'
export function HeroImage(): ReactNode {
return (
<img
className="responsive-image"
src="/images/harbor-1280.webp"
srcSet="/images/harbor-640.webp 640w, /images/harbor-1280.webp 1280w"
sizes="(max-width: 80rem) 100vw, 80rem"
width={1280}
height={720}
loading="eager"
fetchPriority="high"
alt="Fishing boats returning to the harbor at sunrise"
/>
)
}
fetchPriority="high" ist ein Hinweis auf die relative Priorität, keine Garantie für die
Reihenfolge der Anfragen. Verwenden Sie ihn nur für das durch Messungen ermittelte LCP-Bild.
Wenn mehrere Bilder als hochpriorisiert markiert sind, erhält der Browser weniger hilfreiche
Informationen dazu, welches Bild am wichtigsten ist.
React 19 kann beim serverseitigen Rendering auch automatisch einen Bild-Preload ausgeben. Prüfen Sie das erzeugte HTML, bevor Sie einen manuellen Preload hinzufügen. Die Bilddokumentation von React beschreibt die Bedingungen. Ein Preload sollte dieselben responsiven Bildvarianten und dieselbe Breite der Darstellungsfläche wie das Bild verwenden.
Ein Bild kann auf Mobilgeräten das LCP-Element sein, auf Desktopgeräten dagegen nicht, oder umgekehrt. Bestätigen Sie mit Leistungsdaten aus der tatsächlichen Nutzung, welches Element für reale Besucher zum LCP wird. Gehen Sie nicht davon aus, dass jedes große Bild oder jede Karussell-Folie kritisch ist.
Lazy Loading mit responsiven Bildern kombinieren
Auch eine verzögerte Anfrage kann eine unnötig große Datei herunterladen. Bei Breitendeskriptoren
wie 480w liefert srcSet die verfügbaren Dateien,
und sizes beschreibt die Breite der gerenderten Bildfläche.
Der Browser kombiniert diese Informationen mit der Pixeldichte des Geräts und wählt eine Variante.
Geben Sie sizes passend zum Layout an, nicht zur Quelldatei. In dieser
Vorschau füllt das Werkstattfoto den sichtbaren Bereich bis zu einer Spaltenbreite von 40rem aus,
das Hero-Bild bis zu 80rem. Das folgende CSS setzt diese Grenzen um. Bei der Root-Schriftgröße der
Vorschau von 16 Pixeln entsprechen sie 640 und 1.280 CSS-Pixeln. Innenabstände oder ein schmalerer
Container in Ihrer Anwendung erfordern einen entsprechend kleineren Wert für
sizes.
Der Browser führt dieselbe responsive Bildauswahl für sofort und verzögert geladene Bilder durch.
Die Entscheidung zum Ladeverhalten ersetzt weder passende Varianten noch einen korrekten Wert für
sizes.
Der Browser kann eine größere Variante aus dem Cache wiederverwenden. Bildschirme mit hoher
Pixeldichte benötigen möglicherweise mehr Pixel, als diese zwei Varianten bieten. Prüfen Sie
currentSrc bei leerem Cache, statt einen Dateinamen als universelle Auswahl
anzusehen. Unter responsive Bilder finden Sie die Berechnung der
Breite der Bildfläche.
Platz reservieren, um Layoutverschiebungen zu vermeiden
Geben Sie width und height für jedes Inhaltsbild an.
Browser berechnen anhand dieser Attribute das Seitenverhältnis, bevor das Bild heruntergeladen
wurde, und reservieren so den passenden Platz im Layout. Ihr CSS kann das Bild weiterhin flexibel
skalieren. Speichern Sie preview.css:
html {
font-size: 16px;
color-scheme: light dark;
}
body {
margin: 0;
}
.responsive-image {
display: block;
width: 100%;
height: auto;
}
.hero-column {
max-width: 80rem;
margin-inline: auto;
}
.image-column {
max-width: 40rem;
margin-inline: auto;
}
.demo-spacer {
min-height: 6000px;
}
Die Attribute müssen das intrinsische Seitenverhältnis des Bildes beschreiben. Ein Element
img kann beispielsweise width={960} und
height={640} verwenden, wenn jede Variante ein Seitenverhältnis von 3:2 hat,
selbst wenn der Browser eine Datei mit 480×320 auswählt. Verwenden gestalterisch angepasste
Quellen unterschiedliche Ausschnitte oder Seitenverhältnisse, müssen ihre Abmessungen die
gewählte Quelle beschreiben, nicht einen davon unabhängigen Fallback.
Reservierter Bildplatz verhindert eine Ursache für Cumulative Layout Shift (CLS), aber nicht jede mögliche Verschiebung. Bildunterschriften, Werbung, Einwilligungsoptionen, Schriftarten und Fehlermeldungen können die Seite weiterhin verschieben.
Die serverseitig gerenderte Vorschau ausführen
Speichern Sie preview.tsx. Der Server stellt nur diese Seite und die vier
bekannten Dateien über Ihre Loopback-Schnittstelle bereit. Er liest die Dateien beim Start ein.
Ein fehlendes Foto oder Stylesheet stoppt daher die Vorschau, bevor sie eine einsatzbereite URL
meldet. Diese statische Vorschau enthält weder Hydration noch eine Konfiguration für die
Bereitstellung in der Produktion.
import { readFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { renderToStaticMarkup } from 'react-dom/server'
import { ImagePage } from './ImagePage.tsx'
async function main(): Promise<void> {
const images = new Map<string, Buffer>()
for (const name of [
'harbor-640.webp',
'harbor-1280.webp',
'workshop-480.webp',
'workshop-960.webp',
]) {
images.set(`/images/${name}`, await readFile(new URL(`./images/${name}`, import.meta.url)))
}
const css = await readFile(new URL('./preview.css', import.meta.url), 'utf8')
const html = '<!doctype html>' + renderToStaticMarkup(
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React image preview</title>
<style>{css}</style>
</head>
<body><ImagePage /></body>
</html>,
)
const port = Number(process.env.PORT ?? 4173)
if (!Number.isInteger(port) || port < 0 || port > 65535) {
throw new Error('PORT must be an integer between 0 and 65535')
}
const server = createServer((request, response) => {
response.setHeader('Cache-Control', 'no-store')
const image = images.get(request.url ?? '')
if (image) {
response.writeHead(200, { 'Content-Type': 'image/webp' })
response.end(image)
} else if (request.url === '/') {
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
response.end(html)
} else {
response.writeHead(404)
response.end('Not found')
}
})
server.on('error', (error) => {
console.error(`Could not start image preview: ${error.message}`)
process.exitCode = 1
})
process.once('SIGINT', () => { server.close() })
server.listen(port, '127.0.0.1', () => {
const address = server.address()
if (address && typeof address !== 'string') {
console.log(`Image preview: http://127.0.0.1:${address.port}/`)
}
})
}
main().catch((error: unknown) => {
console.error(error)
process.exitCode = 1
})
Erstellen und starten Sie die Vorschau aus ihrem Verzeichnis. Die leere Einstellung
--tsconfig-raw verhindert, dass die TypeScript-Konfiguration eines übergeordneten
Projekts die JSX-Laufzeit dieser Vorschau verändert. Dabei wird die generierte Datei
preview.mjs ersetzt. Ihre Fotos werden nicht verändert.
&& verhindert, dass nach einem fehlgeschlagenen Build eine ältere
generierte Datei gestartet wird.
corepack yarn exec esbuild preview.tsx --bundle --platform=node --format=esm --packages=external --jsx=automatic --tsconfig-raw='{}' --outfile=preview.mjs && node preview.mjs
Öffnen Sie die ausgegebene URL und scrollen Sie zum Werkstattfoto. Drücken Sie im selben Terminal Ctrl+C, um den Server zu stoppen. Meldet der Start eine fehlende Datei, korrigieren Sie deren Namen oder Speicherort und wiederholen Sie den Befehl zum Erstellen und Starten. Wählen Sie nach einer Fehlermeldung wegen eines belegten Ports in einer POSIX-Shell einen verfügbaren Port:
PORT=0 node preview.mjs
Der Server gibt die neue URL aus.
Wann Intersection Observer sinnvoll ist
Verwenden Sie für normale Bilder das native Ladeverhalten des Browsers. Greifen Sie nur dann zu
Intersection Observer, wenn die Komponente ein Verhalten benötigt, das
loading="lazy" nicht ausdrücken kann. Dazu gehören etwa das Starten einer Animation,
das Erfassen eines Sichtbarkeitsereignisses oder das Anwenden eines aufwendigen dekorativen
Hintergrunds kurz bevor dieser den sichtbaren Bereich erreicht.
Binden Sie ein normales Inhaltsbild nicht erst ein, nachdem ein Observer ausgelöst wurde, sofern
dieses Verhalten nicht tatsächlich erforderlich ist. Das bedingte Einbinden verzögert die
Entdeckung, schafft eine zusätzliche Fehlerquelle durch JavaScript und erfordert einen stabilen
Fallback. Inhaltlich relevante Bilder sollten Elemente img oder
picture mit hilfreichem Alternativtext bleiben.
React.lazy() löst ein anderes Problem: Es verzögert das Laden eines
JavaScript-Komponentenmoduls. Es verzögert nicht automatisch die Bildanfragen, die diese
Komponente beim Rendern auslöst.
Responsive Bildvarianten mit Transloadit erzeugen
Lazy Loading kann ein zu großes Quellbild nicht korrigieren. Eine praxistaugliche Pipeline zur Auslieferung erzeugt eine kleine, begrenzte Auswahl an Breiten, optimiert jedes Ergebnis, exportiert die Ergebnisse in einen dauerhaften Speicher und erfasst ihre URLs und Abmessungen für die React-Ansicht.
Der Robot /image/resize von Transloadit kann Breitenvarianten erzeugen, und der Robot /image/optimize kann unterstützte Formate optimieren. Erzeugen Sie die Varianten vor dem Rendern der Seite, exportieren Sie sie in Ihren Speicher und erfassen Sie neben ihren dauerhaften URLs auch ihre tatsächlichen Abmessungen. Eine Quelle, die kleiner als die Zielbreite ist, kann ein kleineres Ergebnis liefern. Versehen Sie diese URL nicht mit einem Breitendeskriptor, den das Ergebnis nicht erfüllt. Informationen zu Speicherintegrationen finden Sie beim Dienst für den Dateiexport, Transformationen auf Anfrage bei Smart CDN.
Die React-Anwendung bleibt für die Darstellung verantwortlich: welches Bild inhaltlich relevant ist, seinen Alternativtext, die Größe seiner Darstellungsfläche und ob es für die initiale Ansicht kritisch ist. Transloadit übernimmt die Medienverarbeitung. Welches Bild in einem bestimmten Seitenlayout zum LCP wird, entscheidet Transloadit nicht.
Das Ergebnis messen
Prüfen Sie die Produktionsseite, statt sich nur auf den Quellcode der Komponente zu verlassen:
- Prüfen Sie die initiale HTML-Antwort.
srcundsrcsetdes Hero-Bildes sollten bereits vorhanden sein. - Zeichnen Sie in den Entwicklertools des Browsers Ladevorgänge mit schmalem und breitem sichtbarem Bereich bei leerem Cache auf. Bestätigen Sie, dass die Anfrage für das Hero-Bild durch dieses HTML beginnt, ohne auf das JavaScript der Anwendung zu warten.
- Prüfen Sie am Anfang der Vorschau, dass das weit entfernte Werkstattfoto noch nicht angefragt wurde. Scrollen Sie darauf zu und beobachten Sie den Beginn der Anfrage. Natives Lazy Loading kann das Bild bereits abrufen, bevor es sichtbar ist.
- Prüfen Sie für jedes Bild
currentSrcund öffnen Sie diese URL, um die Pixelmaße der Datei zu kontrollieren. Der WertnaturalWidthdes Elements kann an die Pixeldichte angepasst sein. Vergleichen Sie die ausgewählte Datei mit der gerenderten Bildfläche und dem Pixelverhältnis des Geräts. Beispielsweise wählte Chrome bei leerem Cache und einem Pixelverhältnis von 1 in dieser Vorschau die Werkstattdatei mit 480 Pixeln für einen sichtbaren Bereich von 390 Pixeln und die Datei mit 960 Pixeln für die Desktopspalte mit 640 Pixeln. - Verzögern Sie Bildantworten und vergleichen Sie den Bildbereich und die Position der Bildunterschrift vor und nach dem Laden. Blockieren Sie außerdem Anfragen, um Alternativtext und reservierten Platz im Fehlerfall zu prüfen.
Diese Prüfungen zeigen, wie Entdeckung, Auswahl und Layout auf einer lokalen Seite funktionieren. Sie belegen keine LCP-Verbesserung für Besucher. Ermitteln Sie in Ihrer Anwendung das tatsächliche LCP-Element und messen Sie LCP und CLS mit Daten aus der tatsächlichen Nutzung, gruppiert nach Seitentemplate und relevantem sichtbarem Bereich. Die LCP-Empfehlungen von web.dev erklären, wie die Entdeckung von Ressourcen zur Ladeverzögerung beiträgt.
Abstände und zeitliche Planung beim Lazy Loading hängen von der Browserimplementierung ab. Vermeiden Sie Regeln wie „alle Bilder nach dem dritten verzögert laden“ oder die Annahme eines festen Pixelschwellenwerts. Messen Sie die Seiten und Geräte, die Ihre Nutzer tatsächlich verwenden.
Häufige Fehler beim Lazy Loading in React
- Das Hero-Bild oder das durch Messungen ermittelte LCP-Bild verzögert laden.
- Die einzige Bild-URL von
srcnachdata-srcverschieben. - Kritische Bilder erst rendern, nachdem clientseitiger Code oder eine Datenanfrage abgeschlossen ist.
srcSetohne einen korrekten Wert fürsizesangeben.widthundheightweglassen, weil CSS später die Bildgröße steuert.- Jedes Bild mit
fetchPriority="high"markieren. - Eine Observer-Bibliothek nur verwenden, um das native Browserverhalten nachzubilden.
- Annehmen, dass
React.lazy()die Netzwerkanfragen für Bilder steuert. - Das Laden einer Quelle mit 3.000 Pixeln verzögern, statt eine passende Variante zu erzeugen.
Die zugrunde liegenden Browserregeln finden Sie im HTML-Standard für Lazy Loading. Bei deaktiviertem Scripting laden Browser diese nativ verzögert zu ladenden Bilder ohne Verzögerung. Tatsächliche URLs im Markup ermöglichen weiterhin das Laden der Inhalte.
