Zum Inhalt springen
Webentwicklung

Core Web Vitals: Performance messen, die Nutzer spueren

9 min Lesezeit
Performance Web Vitals Frontend

Lighthouse zeigt 100 Punkte. Die Seite fliegt — im Lab. Dann oeffnet ein echter Nutzer auf einem Android-Mittelklasse-Handy mit 3G-Verbindung die gleiche Seite und wartet vier Sekunden auf den ersten sichtbaren Inhalt. Das Problem: Synthetische Benchmarks messen ideale Bedingungen. Core Web Vitals messen, was Nutzer tatsaechlich erleben.

Warum synthetische Benchmarks luegen

Lighthouse läuft auf einem leistungsstarken Entwicklerrechner mit stabiler Verbindung. Die Ergebnisse sind reproduzierbar, aber sie spiegeln nicht die Realitaet wider. Google verwendet für die Bewertung in der Search Console das 75. Perzentil der Felddaten — also die Erfahrung des langsamsten Viertels eurer Nutzer. Nicht den Median, nicht den Durchschnitt: P75.

Das bedeutet: Selbst wenn 74 Prozent eurer Nutzer eine schnelle Erfahrung haben, reicht ein schwaches Viertel, um die Bewertung auf “schlecht” zu druecken. Lab-Daten sind gut für Debugging, aber Felddaten entscheiden über Rankings und Nutzererfahrung.

LCP: Largest Contentful Paint

LCP misst, wann das groesste sichtbare Element im Viewport fertig gerendert ist. Typischerweise ein Hero-Image, ein Hintergrundvideo oder ein grosser Textblock. Google bewertet unter 2.5 Sekunden als gut, über 4 Sekunden als schlecht.

Die häufigsten Bottlenecks: Render-blockierende Ressourcen, unkomprimierte Bilder, fehlende Preload-Hints und langsame Server-Response-Zeiten (TTFB).

Preload und fetchpriority

Der Browser entdeckt Bilder erst, wenn der HTML-Parser das entsprechende Element erreicht. Bei Hero-Images ist das zu spät. rel="preload" und das fetchpriority-Attribut lösen genau dieses Problem.

LCP-Optimierung: Preload und fetchpriority html
<!-- Im <head>: Fruehzeitig laden -->
<link rel="preload"
      href="/hero-image.webp"
      as="image"
      type="image/webp"
      fetchpriority="high">

<!-- Im Body: fetchpriority direkt am Element -->
<img src="/hero-image.webp"
     alt="Hero"
     width="1200"
     height="600"
     fetchpriority="high"
     decoding="async">

<!-- Bilder unterhalb des Folds: niedrige Prioritaet -->
<img src="/teaser.webp"
     alt="Teaser"
     loading="lazy"
     fetchpriority="low"
     decoding="async">

Entscheidend: fetchpriority="high" nur für das LCP-Element verwenden. Wenn alles hohe Prioritaet hat, hat nichts hohe Prioritaet. Bilder unterhalb des sichtbaren Bereichs bekommen loading="lazy" und fetchpriority="low", damit sie dem LCP-Element keine Bandbreite stehlen.

Server-seitiges Rendering und TTFB

LCP kann nie schneller sein als die Time to First Byte. Wenn der Server 800ms braucht, um HTML auszuliefern, startet das gesamte Rendering mit 800ms Verzoegerung. Hier helfen CDN-Caching, Edge-Rendering und bei dynamischen Seiten Stale-While-Revalidate-Strategien.

INP: Interaction to Next Paint

INP hat First Input Delay (FID) als Metrik abgeloest und ist deutlich anspruchsvoller. FID mass nur die Verzoegerung der ersten Interaktion. INP misst die schlechteste Interaktionslatenz während des gesamten Seitenbesuchs — vom Klick bis zum nächsten Frame-Update. Unter 200ms ist gut, über 500ms ist schlecht.

Das Problem: INP erwischt alles, was den Main Thread blockiert. Teure Event-Handler, unkontrolliertes Re-Rendering, synchrone DOM-Manipulationen. Und anders als bei LCP, das einmal gemessen wird, bewertet INP die gesamte Session.

Long Tasks aufbrechen

Jede Aufgabe, die den Main Thread laenger als 50ms blockiert, ist ein Long Task. Bei INP sind diese Long Tasks der Hauptfeind. Die Lösung: Arbeit in kleinere Einheiten aufteilen und dem Browser zwischen den Einheiten die Moeglichkeit geben, auf Nutzereingaben zu reagieren.

Main Thread mit scheduler.yield() freigeben typescript
// Schlecht: Eine große Aufgabe blockiert den Main Thread
function processAllItems(items: DataItem[]) {
  for (const item of items) {
    heavyComputation(item);  // 200ms+ am Stueck
    updateDOM(item);
  }
}

// Besser: Nach jedem Chunk dem Browser Zeit geben
async function processItemsInChunks(items: DataItem[]) {
  const CHUNK_SIZE = 5;

  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);

    for (const item of chunk) {
      heavyComputation(item);
      updateDOM(item);
    }

    // Dem Browser die Kontrolle zurückgeben
    if ('scheduler' in globalThis && 'yield' in scheduler) {
      await scheduler.yield();
    } else {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
}

scheduler.yield() ist die moderne API dafür — sie gibt dem Browser die Moeglichkeit, anstehende Nutzerinteraktionen zu verarbeiten, bevor die nächste Aufgabe startet. Der Fallback über setTimeout(0) funktioniert aehnlich, hat aber den Nachteil, dass die Aufgabe am Ende der Task-Queue landet statt priorisiert zu werden.

CLS: Cumulative Layout Shift

CLS misst, wie stark sich sichtbare Elemente während des Ladevorgangs verschieben. Jeder kennt das: Man will einen Button klicken, ein Werbebanner laedt nach, der Button springt nach unten, man klickt auf die Werbung. Unter 0.1 ist gut, über 0.25 ist schlecht.

Die drei Hauptursachen:

Bilder und Videos ohne Dimensionen: Ohne width und height weiß der Browser nicht, wie viel Platz reserviert werden muss. Sobald das Bild laedt, verschiebt sich alles darunter.

Dynamisch eingefuegter Content: Cookie-Banner, Newsletter-Popups, Ads — alles, was nach dem initialen Render ins DOM geschoben wird, verursacht Layout Shifts. Die Lösung: Platz vorab reservieren oder Content per transform animieren statt per Layout-Änderung.

Web Fonts: Wenn ein Custom Font laedt und andere Metriken hat als der Fallback-Font, springt der Text. font-display: swap allein reicht nicht — es braucht passende Fallback-Fonts mit angepassten Metriken.

Layout Shifts durch Fonts vermeiden css
/* Fallback-Font mit angepassten Metriken */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-display: swap;
  /* Metriken des Fallbacks an den Custom Font anpassen */
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* Platz für Bilder immer reservieren */
img, video {
  max-width: 100%;
  height: auto;
  /* Aspect-Ratio aus width/height-Attributen */
  aspect-ratio: attr(width) / attr(height);
}

/* Dynamischen Content per transform statt layout */
.notification-enter {
  transform: translateY(-100%);
  transition: transform 0.3s ease;
}
.notification-enter.visible {
  transform: translateY(0);
}

Die size-adjust- und override-Properties sind der Schluessel für FOUT-freie Font-Wechsel. Tools wie Fontaine oder next/font berechnen diese Werte automatisch.

Messen in Production: RUM statt raten

Lab-Tools reichen nicht. Fuer belastbare Daten braucht es Real User Monitoring (RUM). Die web-vitals-Library von Google ist der De-facto-Standard dafür.

web-vitals Library: Core Web Vitals an Analytics senden typescript
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric: {
  name: string;
  value: number;
  id: string;
  rating: 'good' | 'needs-improvement' | 'poor';
}) {
  // An euer Analytics-Backend senden
  navigator.sendBeacon('/api/vitals', JSON.stringify({
    name: metric.name,
    value: Math.round(metric.value),
    rating: metric.rating,
    id: metric.id,
    url: location.href,
    // Geräteklasse mitschicken für Segmentierung
    deviceMemory: (navigator as any).deviceMemory ?? 'unknown',
    connection: (navigator as any).connection?.effectiveType ?? 'unknown',
  }));
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

Entscheidend ist die Segmentierung. P75 über alle Nutzer ist ein Anfang, aber die wirklichen Erkenntnisse kommen aus der Aufschluesselung nach Geräteklasse, Verbindungstyp und Seitentyp. Eine Landingpage mit 2.1s LCP ist akzeptabel — dieselbe Zahl auf einer Produktseite mit einem einzelnen Bild deutet auf ein echtes Problem hin.

Der CrUX-Report (Chrome User Experience Report) liefert oeffentliche Felddaten für jede Seite mit genuegend Traffic. Die CrUX API ist kostenlos und laesst sich in Dashboards integrieren. Fuer Seiten mit weniger Traffic bleibt RUM die einzige Option.

Framework-spezifische Fallstricke

Frameworks bringen eigene Performance-Herausforderungen mit. React-Hydration ist ein klassisches Beispiel: Der Server rendert HTML, der Client muss das gesamte Component-Tree nochmals durchlaufen, um Event-Handler anzuhaengen. Waehrend der Hydration ist die Seite sichtbar, aber nicht interaktiv — ein INP-Killer.

SSR vs. SSG: Server-Side Rendering verbessert LCP gegenueber reinem Client-Side Rendering, aber die Hydration-Kosten bleiben. Static Site Generation eliminiert TTFB-Probleme fast vollständig, funktioniert aber nur für Seiten ohne nutzerspezifischen Content. Islands Architecture — wie Astro es implementiert — sendet JavaScript nur für interaktive Komponenten und laesst den Rest als statisches HTML. Das ist für Content-lastige Seiten der beste Kompromiss.

Selective Hydration in React 18+: Suspense-Boundaries erlauben priorisierte Hydration. Interaktive Elemente im Viewport werden zuerst hydriert, der Rest wartet. Das verbessert INP, erfordert aber bewusstes Komponentendesign.

Diminishing Returns: Wann Optimierung zur Überoptimierung wird

Performance-Optimierung hat abnehmenden Grenznutzen. Der Sprung von 4s LCP auf 2.5s ist für Nutzer deutlich spürbar und verbessert Conversion-Rates messbar. Der Sprung von 2.2s auf 1.8s? Kaum wahrnehmbar. Der Sprung von 1.8s auf 1.4s? Statistisch irrelevant für die meisten Anwendungen.

Die sinnvolle Grenze: Wenn alle drei Core Web Vitals im gruenen Bereich liegen (LCP unter 2.5s, INP unter 200ms, CLS unter 0.1), ist die Basis geschaffen. Weitere Optimierung lohnt sich nur, wenn Monitoring zeigt, dass spezifische Nutzersegmente schlechte Werte haben — etwa mobile Nutzer in Regionen mit schwacher Infrastruktur.

Was sich nicht lohnt: Stunden in die Optimierung einer Seite investieren, die 50 Besucher pro Monat hat. Oder jedes Bild manuell in sechs Formate konvertieren, wenn ein CDN das automatisch erledigt. Oder ein Framework wechseln, weil Lighthouse 5 Punkte mehr verspricht.

Fazit

Core Web Vitals sind kein Selbstzweck und kein SEO-Trick. Sie messen, ob eine Seite schnell laedt, zuverlässig auf Eingaben reagiert und visuell stabil bleibt. Die wichtigste Erkenntnis: Messt im Feld, nicht im Lab. Optimiert gegen P75, nicht gegen den Idealfall. Und hoert auf zu optimieren, wenn die Werte gruen sind — es gibt produktivere Dinge zu tun, als die letzte Millisekunde aus einem LCP herauszuquetschen.