Zum Inhalt springen
PS

18. Juli 2026 ·

Core Web Vitals auf einer Next.js-Marketing-Site: LCP von 4s auf 1,2s


Ein Kunde kam mit einer Next.js-Marketing-Seite zu mir, die auf meinem Laptop ordentlich aussah und sich auf dem Smartphone kaputt anfühlte. Die Zahl, auf die es ankam, war Largest Contentful Paint: 4.0s auf einem Android-Gerät der Mittelklasse über 4G. Googles Schwellenwert für "gut" liegt bei 2.5s. Die Seite verlor die Besucher, für deren Akquise sie Anzeigen bezahlt hatte, noch bevor der Hero überhaupt gerendert war.

Hier ist genau das, was ich gefunden habe, was ich geändert habe und wie viel jede Änderung tatsächlich gebracht hat. Kein Wundermittel — LCP ist der Tod durch tausend Schnitte, und man behebt ihn genauso.

Zuerst messen, unter den richtigen Bedingungen

Der Fehler, den ich am häufigsten sehe: Leute optimieren gegen einen Desktop-Lighthouse-Lauf über eine schnelle Verbindung und erklären den Sieg. Dort sind die Nutzer aber nicht.

Ich messe LCP auf drei Wegen:

  • Lighthouse in den Chrome DevTools, aber mit Mobile-Emulation und dem Preset "Slow 4G" + 4× CPU-Throttling. Das ist deine reproduzierbare Lab-Zahl.
  • WebPageTest auf einem echten Geräteprofil, denn die Emulation lügt über die CPU-Kosten.
  • Felddaten aus dem Chrome UX Report (CrUX) — das 75. Perzentil echter Besucher. Das Lab sagt dir, was möglich ist; das Feld sagt dir, was tatsächlich passiert.

Auf dieser Seite lag die Lab-Zahl bei 4.0s und das Feld-p75 war schlechter. Gut — sie waren sich in der Richtung einig.

Das LCP-Element finden

Öffne das Performance-Panel, zeichne einen Ladevorgang auf und schau dir den "LCP"-Marker an. Die DevTools nennen dir das exakte Element. Hier war es das Hero-<img> — ein 1.4 MB großes JPEG, gerendert über die volle Viewport-Breite, ausgeliefert in seiner Originalgröße von 3000px und nicht über next/image.

Diese eine Tatsache erklärte den Großteil der 4 Sekunden: ein riesiges, unoptimiertes, quasi render-blockierendes Bild ohne Priority-Hint, ohne modernes Format und ohne Breitenbeschränkung.

Fix 1 — das Hero-Bild (der große Brocken)

Das Hero-Bild auf next/image mit einem priority-Hint umzustellen brachte mehr als alles andere zusammen:

import Image from "next/image";
import hero from "@/public/hero.jpg";

export function Hero() {
  return (
    <Image
      src={hero}
      alt="..."
      priority
      sizes="100vw"
      className="h-[60vh] w-full object-cover"
      placeholder="blur"
    />
  );
}

Was dir die einzelnen Teile bringen:

  • priority lädt das Bild vor und nimmt es aus dem Lazy-Loading heraus, sodass der Browser dein LCP-Element sofort abruft, statt es spät zu entdecken.
  • Automatisches AVIF/WebP reduzierte den Transfer von 1.4 MB auf ~180 KB.
  • sizes="100vw" lässt Next korrekt dimensionierte Quellen generieren, sodass ein Smartphone ein handygroßes Bild lädt und kein 3000px-Bild.
  • placeholder="blur" beseitigt das irritierende Aufpoppen und stabilisiert das Layout.

LCP nach dieser einen Änderung: 4.0s → 2.1s.

Fix 2 — Schriften, die nicht mehr blockieren

Die Seite lud zwei Web-Fonts von einem Drittanbieter-CDN mit einem Standard-<link>. Das ist eine render-blockierende Anfrage auf dem critical path, plus ein Layout-Shift, wenn die Schrift eingewechselt wird.

next/font hostet die Dateien selbst, entfernt die zusätzliche DNS-Auflösung/Verbindung und kümmert sich um font-display für dich:

import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
});

Das Self-Hosting eliminierte einen Round-Trip zu einem Drittanbieter-Origin, und das swap-Verhalten sorgte dafür, dass Text sofort in einem Fallback gezeichnet wurde und dann aufgewertet wurde. Kleinerer Gewinn beim LCP, größerer Gewinn beim CLS.

LCP: 2.1s → 1.8s.

Fix 3 — weniger JavaScript ausliefern

Die Hero-Section war aus einem einzigen Grund eine Client Component ("use client"): ein einzelner animierter Zähler weit unterhalb des Folds. Das zwang den gesamten Teilbaum, zu hydratisieren, bevor er zur Ruhe kam.

Ich machte die Seite zu einer Server Component und schob "use client" hinunter zu dem einen Leaf, das tatsächlich Interaktivität brauchte. Weniger ausgeliefertes JS, weniger Main-Thread-Arbeit, die mit dem Dekodieren des Bildes konkurriert.

Die Regel, an die ich mich jetzt halte: eine Komponente ist eine Server Component, bis das Gegenteil bewiesen ist. Interaktivität ist eine Angelegenheit der Blätter, nicht der Seite.

LCP: 1.8s → 1.4s.

Fix 4 — Drittanbieter-Skripte vom critical path holen

Analytics und ein Chat-Widget wurden mit einfachen <script>-Tags im <head> geladen. next/script mit der richtigen Strategie verzögert sie so lange, bis sie dem Paint nicht mehr schaden können:

import Script from "next/script";

<Script src="https://widget.example.com/chat.js" strategy="lazyOnload" />;
  • afterInteractive für Dinge, die du bald willst, aber nicht blockierend (die meisten Analytics).
  • lazyOnload für alles Nicht-Essentielle (Chat, Heatmaps).

Allein das Chat-Widget stahl während des Ladens ~300ms des Main Threads. Es zu verzögern war geschenkt.

LCP: 1.4s → 1.2s.

Fix 5 — statisch rendern, wo es geht

Die Startseite holte bei jedem Request CMS-Inhalte ohne jegliches Caching, sodass jeder Besuch für den Round-Trip bezahlte. Der Inhalt dieser Seite ändert sich vielleicht einmal die Woche. Sie statisch zu rendern (und nach einem Zeitplan zu revalidieren) bedeutete, dass die HTML-Hülle sofort vom Edge ausgeliefert wurde und der LCP überhaupt nicht mehr von einem Origin-Fetch abhing.

Vorher/Nachher

| Metrik (Mobile, Slow 4G) | Vorher | Nachher | | ------------------------ | ------ | ----- | | LCP | 4.0s | 1.2s | | Hero-Transfer | 1.4 MB | 180 KB | | Gesamt-JS | hoch | ~40% weniger | | CLS | 0.18 | 0.02 |

Was du zuerst prüfen solltest

Wenn du eine Stunde Zeit und eine langsame Next.js-Seite hast, mach das in dieser Reihenfolge:

  1. Finde das LCP-Element in den DevTools. Es ist fast immer ein Bild.
  2. Schick es durch next/image mit priority und einem echten sizes.
  3. Verschiebe Schriften zu next/font.
  4. Lösche ein "use client" — schieb es zu dem Leaf, das es braucht.
  5. Schick jedes Drittanbieter-Skript durch next/script.

Nichts davon ist raffiniert. LCP belohnt Disziplin, keine Tricks: liefere die richtig dimensionierten Bytes für das wichtigste Element aus und halte alles andere davon ab, ihm in die Quere zu kommen.