Zum Inhalt springen

next/image repariert Ihren LCP nicht

Die Komponente optimiert Bytes. Über den LCP entscheidet, wann die Anfrage startet - meist die Auffindreihenfolge, ein lazy geladenes Hero oder eine blockierende Schrift.

6 Min. Lesezeit

Teams kommen zu uns, nachdem sie bereits jedes <img> durch next/image ersetzt haben, und der LCP hat sich nicht bewegt. Das ist das erwartbare Ergebnis, und es lohnt sich zu verstehen, warum - denn dieselbe Überlegung zeigt, was stattdessen geholfen hätte.

next/image optimiert die Nutzlast: ein kleineres Format, die passenden Maße für das Gerät, kein Layout-Shift. Der LCP misst den Zeitpunkt des größten Paints. Bytes sind eine Eingangsgröße dafür, und meistens nicht die bindende.

Was darüber entscheidet, wann die Anfrage startet

Der Browser kann nur holen, was er gefunden hat. Für das Hero-Bild gibt es drei Möglichkeiten, und sie liegen eine Größenordnung auseinander:

Wie das Bild gefunden wirdWann die Anfrage startet
<img> im initialen HTML, eagerBeim Preload-Scan, vor dem CSS
<img> im initialen HTML, lazyNach dem Layout, sobald der Browser weiß, dass es sichtbar ist
Von JavaScript nach der Hydration eingefügtNachdem das Bundle geladen, geparst und ausgeführt wurde

next/image lädt standardmäßig lazy. Die übliche Migration macht also aus einem eager geladenen Hero ein lazy geladenes, verzögert seine Anfrage bis nach das Layout und verschlechtert den LCP - während jedes Bild-Audit in Lighthouse grün wird.

Die Korrektur ist ein Prop:

import Image from 'next/image';
 
export function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="..."
      width={1600}
      height={900}
      priority          // eager + fetchpriority="high" + ein Preload-Hinweis
      sizes="100vw"
      className="w-full h-auto"
    />
  );
}

priority auf genau einem Bild pro Route - auf dem, das das LCP-Element ist. Es auf sechs Bilder zu setzen ist dasselbe, wie es auf keines zu setzen, weil alle um dieselbe Verbindung konkurrieren.

Der sizes-Fehler, der mehr kostet als das Format

sizes sagt dem Browser, welchen Kandidaten aus srcset er nehmen soll, und wird vor dem Layout ausgewertet. Steht dort das Falsche, laden Sie ein 1600px-Bild für einen 400px-Platz - oder, schlimmer für die Qualität, ein 400px-Bild für ein vollflächiges Banner.

Der Standard beim Weglassen ist 100vw, was für ein vollbreites Hero richtig und für alles andere falsch ist:

// Ein Kartenbild in einem dreispaltigen Raster.
<Image
  src={post.cover}
  alt=""
  width={800}
  height={450}
  sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
/>

Dieses eine Attribut ist meist mehr wert als die AVIF-Konvertierung, weil es ändert, welche Datei angefragt wird, statt wie viele Bytes diese Datei hat.

Wenn das LCP-Element Text ist

Auf einer Content-Website ist es das meistens, und dann geht die Bildarbeit am Thema vorbei. Die Überschrift kann nicht painten, bevor ihre Schrift aufgelöst ist; die Kette lautet also HTML → CSS → Schriftdatei → Paint.

import { Inter } from 'next/font/google';
 
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',   // im Fallback painten, tauschen wenn die Datei da ist
  variable: '--font-sans',
});

next/font hostet die Datei selbst und gibt ein Preload aus, was die Drittanbieter-Verbindung aus der Kette nimmt. display: 'swap' ist das, was die Überschrift überhaupt nicht mehr warten lässt - der Preis ist ein sichtbarer Tausch, den Sie über einen Fallback mit ähnlichen Metriken verkleinern und nicht dadurch, dass Sie den Paint blockieren.

Die andere Hälfte ist der Fallback-Stack. font-family: var(--font-sans), sans-serif lässt den Browser sofort in einer Schrift painten, deren Metriken Sie nicht gewählt haben; eine nahe Systemschrift zu benennen macht den Tausch deutlich unauffälliger.

Das Richtige messen

Lighthouse meldet Labor-LCP bei einem kalten, gedrosselten Ladevorgang. Felddaten melden, was Ihre Besucher erlebt haben, inklusive Cache-Treffern und echter Geräte. Die beiden widersprechen sich ständig, und die Feldzahl ist die maßgebliche.

'use client';
 
import { useReportWebVitals } from 'next/web-vitals';
 
export function Vitals() {
  useReportWebVitals((metric) => {
    if (metric.name !== 'LCP') return;
    // Das Element, nicht nur die Zahl - "LCP ist 3,1 s" ist nicht
    // handlungsfähig, "LCP ist 3,1 s und es ist das Hero-Bild" schon.
    navigator.sendBeacon('/api/vitals', JSON.stringify({
      value: metric.value,
      element: metric.attribution?.element,
      url: location.pathname,
    }));
  });
 
  return null;
}

Loggen Sie das Element. Die Hälfte unserer Performance-Projekte beginnt damit, dass das LCP-Element nicht das ist, was das Team annahm - ein Cookie-Banner, ein verborgenes <h1>, ein leerer Container, der zufällig groß ist.

Die vollständige Reihenfolge, was zuerst zu beheben ist, steht in Core Web Vitals in Next.js: was die Zahlen wirklich bewegt.

Die Kurzfassung

  1. Finden Sie heraus, welches Element der LCP ist - aus Felddaten, pro Route.
  2. Ist es ein Bild: priority auf genau dieses eine, korrektes sizes, und sorgen Sie dafür, dass nichts darüber im HTML den Parser blockiert.
  3. Ist es Text: next/font mit display: 'swap' und ein Fallback mit nahen Metriken.
  4. Erst danach über Format und Qualität nachdenken.

In Schritt eins bis drei liegen die Sekunden. In Schritt vier liegen die Audits - weshalb er derjenige ist, der zuerst gemacht wird.

Zurück zu allen Artikeln