Zum Inhalt springen

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.

4 Min. Lesezeit

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 client Sie wirklich kostet.
  • Eine schwere Abhängigkeit im Modul-Scope importiert. Chart-Bibliotheken, Datumsbibliotheken, Editoren. Laden Sie sie mit next/dynamic und ssr: false, wenn die Komponente wirklich unterhalb des Falzes liegt.
  • Teure Arbeit in einem Event-Handler. Zehntausend Zeilen in einem onChange filtern. 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 width und height oder packen Sie sie in einen Container mit fixem aspect-ratio. next/image erzwingt 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: swap zusammen mit adjustFontFallback, 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

  1. Felddaten holen. Nach Geräteklasse und Routen-Template segmentieren.
  2. Rendering-Strategie korrigieren — eine dynamische Route, die statisch sein sollte, schlägt alles andere auf dieser Liste.
  3. Das LCP-Element in Ordnung bringen: Priorität, Maße, Format.
  4. Schriften auf next/font umstellen, Drittanbieter-Skripte aus dem kritischen Pfad nehmen.
  5. Die Client-Grenze nach unten ziehen; den Rest per Code-Splitting trennen.
  6. Platz für alles reservieren, was spät eintrifft.
  7. 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.

Zurück zu allen Artikeln