Core Web Vitals in Next.js: Was die Zahlen wirklich bewegt
LCP, INP und CLS haben in einer Next.js-Anwendung konkrete Ursachen. Das ist die geordnete Liste, die wir in Performance-Projekten abarbeiten — mit den Änderungen, die sich lohnen.
Ein Lighthouse-Score ist eine Labormessung eines einzelnen Seitenaufrufs auf einem simulierten Gerät. Felddaten sind das, was Ihre Besucher tatsächlich erlebt haben, und sie sind das, was Google verwendet. Fangen Sie immer dort an:
# p75 field data for an origin, from the Chrome UX Report
curl -s "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$CRUX_KEY" \
-H 'Content-Type: application/json' \
-d '{"origin":"https://example.com","formFactor":"PHONE"}'Wenn Ihr Laborwert 95 beträgt und Ihr Feld-LCP 3,8 s, dann belügt Sie das Labor über irgendetwas — meist über eine Schrift, ein Drittanbieter-Skript oder ein reales Netz, das Ihr simuliertes nicht abbildet.
LCP: fast immer das Bild oder die Schrift
Largest Contentful Paint misst, wann das größte Element im sichtbaren Bereich fertig gezeichnet ist. In einer Next.js-Anwendung decken vier Ursachen die große Mehrheit der Fälle ab.
1. Das Hero-Bild ist nicht priorisiert
import Image from 'next/image';
<Image
src="/hero.jpg"
alt=""
width={1200}
height={630}
priority // preloads it; without this it waits in the lazy queue
sizes="100vw"
quality={80}
/>Ohne priority steht Ihr LCP-Element in der Browser-Warteschlange hinter
jeder anderen Ressource. Diese einzeilige Änderung nimmt dem mobilen LCP
regelmäßig eine halbe Sekunde.
Ebenso gilt: priority bei mehr als ein oder zwei Bildern ist derselbe Fehler
mit umgekehrtem Vorzeichen — wenn alles vorgeladen wird, ist nichts
priorisiert.
2. Die Route ist dynamisch, ohne es sein zu müssen
Eine statisch gerenderte Route liefert HTML vom nächstgelegenen Edge-Knoten. Eine dynamische lässt zuerst Ihren Komponentenbaum und Ihre Datenbank laufen. Liegt die Zeit bis zum ersten Byte über 600 ms, rettet keine Bildoptimierung das LCP — der Browser hat noch nichts bekommen, das er zeichnen könnte.
Das ist die Verbindung zwischen Rendering-Strategie und Core Web Vitals, die die meisten Performance-Checklisten vollständig übersehen.
3. Schriften blockieren das Zeichnen
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // paint immediately with the fallback
preload: true,
adjustFontFallback: true, // reduces the shift when the real font arrives
});next/font hostet die Datei selbst und entfernt damit eine DNS-Auflösung,
einen TLS-Handshake und einen Roundtrip zu einer fremden Domain aus Ihrem
kritischen Pfad. Wenn Sie Schriften noch per <link> von einem Font-CDN
laden, liegt dort eine kostenlose Verbesserung auf dem Tisch.
4. Ein Drittanbieter-Skript steht im Head
Tag-Manager, Chat-Widgets, Consent-Banner, Session-Recorder. Jedes davon ist eine blockierende Anfrage im kritischen Pfad, solange es nicht ausdrücklich verzögert wird:
import Script from 'next/script';
<Script src="https://example.com/widget.js" strategy="lazyOnload" />In Audits ist in etwa einem Drittel der Fälle ein Drittanbieter-Skript der größte einzelne LCP-Verursacher. Es lohnt sich, das zu messen, bevor Sie einen Sprint in den eigenen Code stecken.
INP: der Preis der Hydratation
Interaction to Next Paint hat First Input Delay abgelöst, weil FID das Falsche gemessen hat — es hat nur die Verzögerung bis zum Start des Handlers gestoppt, nicht wie lange die Besucherin auf ein Ergebnis gewartet hat.
INP-Probleme sind in Next.js Bundle-Probleme. Die Ursachen, in der Reihenfolge, in der wir sie finden:
- Eine Client-Grenze zu weit oben im Baum. Ausführlich in unserem Artikel
dazu, was
use clientSie wirklich kostet. - Eine schwere Abhängigkeit im Modul-Scope importiert. Chart-Bibliotheken,
Datumsbibliotheken, Editoren. Laden Sie sie mit
next/dynamicundssr: false, wenn die Komponente wirklich unterhalb des Falzes liegt. - Teure Arbeit in einem Event-Handler. Zehntausend Zeilen in einem
onChangefiltern. Auf den Server verlagern, oder entprellen und virtualisieren. - Zu viele Hydratations-Wurzeln. Hundert unabhängig interaktive Karten auf einer Listenseite sind hundert Hydratationseinheiten, die um den Haupt-Thread konkurrieren.
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('@/components/Chart'), {
ssr: false,
loading: () => <ChartSkeleton />,
});CLS: reservieren Sie den Platz
Cumulative Layout Shift ist von den dreien am leichtesten zu beheben und am peinlichsten auszuliefern.
- Geben Sie Bildern immer explizite
widthundheightoder packen Sie sie in einen Container mit fixemaspect-ratio.next/imageerzwingt das, was einer der besseren Gründe ist, es zu verwenden. - Reservieren Sie Platz für alles, was spät eintrifft — Werbeplätze, Embeds, Consent-Banner. Ein Skeleton mit den Maßen des endgültigen Elements kostet nichts und beseitigt den Versatz vollständig.
- Nutzen Sie
font-display: swapzusammen mitadjustFontFallback, damit die Fallback-Metriken der echten Schrift nahekommen. - Schieben Sie niemals nach dem Laden ein Banner über bestehenden Inhalt. Legen Sie es darüber, oder reservieren Sie den Platz im initialen HTML.
Das Ergebnis verteidigen
Jede Korrektur oben zerfällt wieder. Jemand fügt eine Abhängigkeit hinzu, jemand ein Skript, jemand verschiebt eine Grenze. Ohne Durchsetzungsmechanismus machen Sie diese Arbeit nächstes Jahr erneut.
# .github/workflows/performance.yml
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
https://staging.example.com/
https://staging.example.com/pricing
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 180 },
{ "resourceType": "total", "budget": 600 }
],
"timings": [{ "metric": "interactive", "budget": 3000 }]
}
]Ein Pull Request, der das JavaScript-Budget über 180 KB hebt, lässt jetzt einen Check scheitern, statt still auszuliefern. Diese Budget-Datei ist das eigentliche Ergebnis eines Performance-Projekts. Der bessere Score ist nur das, was auf dem Weg dorthin passiert.
Die Reihenfolge, in der wir arbeiten
- Felddaten holen. Nach Geräteklasse und Routen-Template segmentieren.
- Rendering-Strategie korrigieren — eine dynamische Route, die statisch sein sollte, schlägt alles andere auf dieser Liste.
- Das LCP-Element in Ordnung bringen: Priorität, Maße, Format.
- Schriften auf
next/fontumstellen, Drittanbieter-Skripte aus dem kritischen Pfad nehmen. - Die Client-Grenze nach unten ziehen; den Rest per Code-Splitting trennen.
- Platz für alles reservieren, was spät eintrifft.
- Ein Budget in die CI legen, damit nichts davon zurückfallen kann.
Schritt zwei und drei sind meist der größte Teil des Gewinns. Der Rest ist, ihn zu behalten.
