CMS-Bilder in Next.js mit getImageProps optimieren
Wenn du Inhalte aus einem CMS wie WordPress renderst, kommen die Bilder womöglich als nackte <img src="..." alt="...">-Tags an: kein srcset, kein Lazy Loading, kein WebP. Next.js kann sie nicht automatisch anfassen, weil sie in einem rohen HTML-String stecken, nicht in einem React-Komponentenbaum. Dieser Beitrag zeigt, wie du Next.js' getImageProps nutzt, um in jedes Bild ein sauberes srcset, WebP-Konvertierung, Lazy Loading und responsive Größenangaben einzuschleusen, bevor es überhaupt im Browser ankommt.
Warum CMS-Bilder die Next.js-Optimierung aushebeln
Die Next.js-Bildoptimierung ist einer der besten Gründe, das Framework zu nutzen. Automatische WebP-Konvertierung, Lazy Loading, korrekte srcset-Generierung: das erledigt alles. Aber es funktioniert nur, wenn du die <Image>-Komponente aus next/image verwendest.
Wenn du den Body eines Blogbeitrags aus WordPress oder einem beliebigen Headless-CMS ziehst, bekommst du einen rohen HTML-String. Dieser String enthält schlichte <img>-Tags mit nichts außer einem src und einem alt. Diese Bilder laden in voller Auflösung, blockieren das Rendering und ruinieren deinen Core-Web-Vitals-Score.
Du hast zwei Optionen: jedes Bild zur Render-Zeit in eine React-<Image>-Komponente parsen oder den HTML-String auf dem Server vorverarbeiten, bevor er beim Client ankommt. Der serverseitige Ansatz ist sauberer: Er trennt die Verantwortlichkeiten und hält deine Render-Komponenten schlank.
Was getImageProps tatsächlich macht
getImageProps ist eine Low-Level-API, die von next/image exportiert wird. Sie stellt dieselbe Optimierungslogik bereit, die die <Image>-Komponente antreibt, aber als schlichter asynchroner Funktionsaufruf, der Props statt JSX zurückgibt.
Du übergibst ihr ein src, ein alt, ein sizes und optional ein width und height. Sie gibt ein props-Objekt zurück, mit einem optimierten src (das auf das Bild-CDN von Next.js zeigt), einem vollständigen srcSet und sinnvollen Defaults für loading und decoding. Diese Props kannst du direkt auf jedes <img>-Element spreaden.
Genau das brauchst du. Statt Komponenten zu rendern, verarbeitest du einen String: Finde die <img>-Tags, führe getImageProps aus und schreibe die optimierten Attribute zurück in den HTML-String.
Das optimizeImages-Utility: wie es funktioniert
Das Utility besteht aus vier kleinen Funktionen, die jeweils genau eine Sache tun. Hier ist, was jeder Teil macht, bevor du die Implementierung einfügst.
parseImgAttributes nimmt einen rohen <img>-Tag-String und gibt ein schlichtes Key-Value-Objekt seiner Attribute zurück. Es entfernt die Syntax des öffnenden Tags und lässt dann ein Regex über den restlichen Text laufen, um Attributnamen und -werte einzufangen. Keys werden kleingeschrieben, sodass attrs.src immer zuverlässig ist, egal wie das CMS das Markup serialisiert hat.
buildImgTag macht das Gegenteil: Es nimmt diese Key-Value-Map und serialisiert sie zurück in einen gültigen <img />-String. Es escaped & und " innerhalb von Attributwerten, damit das HTML gültig bleibt.
optimizeImageTag ist der Kern. Es ruft getImageProps mit dem geparsten src und alt auf und merged die zurückgegebenen Props dann zurück in die ursprüngliche Attribut-Map. Wirft getImageProps aus irgendeinem Grund einen Fehler (eine Domain, die nicht in next.config gelistet ist, eine fehlerhafte URL), fügt der Catch-Block dem ursprünglichen Tag Lazy Loading und asynchrones Decoding hinzu und macht weiter.
Die Konstante CONTENT_IMAGE_SIZES sagt dem Browser, dass das Bild auf Mobilgeräten die volle Viewport-Breite einnimmt und auf breiteren Bildschirmen bei 768px deckelt. Das passt zu einer typischen Blog-Content-Spalte. Passe es an die tatsächlichen Breakpoints deines Layouts an.
Schließlich führt optimizeImages alles zusammen. Es findet mit einem Regex alle <img>-Tags im HTML-String, führt optimizeImageTag auf jedem davon parallel über Promise.all aus und ersetzt dann die ursprünglichen Tags der Reihe nach.
Noch weiter: echte Bildabmessungen abrufen
Vielleicht sind dir die // width, height-Kommentare im ursprünglichen Code aufgefallen. getImageProps generiert genauere srcset-Einträge, wenn es die intrinsischen Abmessungen des Quellbilds kennt. Ohne sie fällt Next.js auf einen generischen Satz von Kandidatenbreiten zurück, der womöglich überhaupt nicht zu deinen Bildern passt.
Abmessungen anzugeben verhindert außerdem Layout Shift. Wenn der Browser das Seitenverhältnis schon vor dem Laden des Bildes kennt, kann er den korrekten Platz im Dokumentfluss reservieren. Das verbessert direkt deinen Cumulative-Layout-Shift-Score (CLS).
Das Paket probe-image-size liest gerade genug Bytes aus einer Bild-URL, um die Abmessungen zu extrahieren, es lädt nicht die ganze Datei herunter. Installiere es zuerst:
npm install probe-image-size
npm install -save-dev @types/probe-image-sizeFüge dann diese Funktion zu deiner Utility-Datei hinzu:
Das HTML sicher rendern mit html-react-parser
Sobald optimizeImages deinen HTML-String verarbeitet hat, musst du ihn in React rendern. Die erste Option, zu der die meisten Entwickler greifen, ist dangerouslySetInnerHTML. Der Name ist nicht nur eine Warnung: Er beschreibt genau, was es tut.
innerHTML direkt zu setzen umgeht React komplett. React kann diese DOM-Knoten nicht reconcilen, kann keine Event-Handler an Elemente darin hängen und kann Cross-Site-Scripting (XSS) nicht verhindern, falls CMS-Content jemals nutzergeneriert oder kompromittiert ist. Du verlierst außerdem die Hydration-Korrektheit: Server und Client können unterschiedliche DOMs erzeugen, was subtile Bugs verursacht.
Das richtige Werkzeug ist html-react-parser. Es parst den HTML-String in einen React-Element-Baum, nicht in rohe DOM-Knoten. React besitzt die Ausgabe, die Hydration funktioniert korrekt, und du kannst mit der replace-Option jedes Element abfangen, um es bei Bedarf gegen eine eigene Komponente auszutauschen.
| Ansatz | XSS-sicher | React-Reconciliation | Event-Handler | Hydration |
|---|---|---|---|---|
dangerouslySetInnerHTML | Nein | Nein | Nein | Unzuverlässig |
html-react-parser | Ja | Ja | Ja | Korrekt |
Der replace-Callback ist auch als Sicherheitsnetz nützlich. Falls ein <img>-Tag durch den serverseitigen Optimierungsschritt geschlüpft ist, kannst du es hier abfangen und stattdessen eine Next.js-<Image>-Komponente rendern. Das gibt dir einen sauberen Einstiegspunkt in den Parse-Baum, ohne dass du deinen eigenen HTML-Walker schreiben musst.
Alles zusammenfügen
Die komplette Pipeline ist unkompliziert. Hole den Body deines Beitrags aus dem CMS, schicke den HTML-String auf dem Server durch optimizeImages und übergib das Ergebnis dann an deine Komponente, um es mit html-react-parser zu rendern. Deine Leser bekommen passend dimensionierte, lazy geladene Bilder im WebP-Format, und du hast weder das CMS angefasst noch ein einziges Template geändert.
Der Ansatz skaliert außerdem gut. Da optimizeImages zur Build-Zeit oder in einer Server-Komponente läuft, gibt es keine clientseitigen Kosten. Die ganze Schwerstarbeit passiert, bevor das HTML den Browser erreicht.