Core Web Vitals Optimization: 5 SaaS-Apps erreichen 95+ in 90 Tagen (Echte Lighthouse-Daten)

95+ CWV-Werte in 90 Tagen: 5 SaaS-Apps erreichen Seite-1-Rankings mit echten Lighthouse- & CrUX-Daten. Holen Sie sich denselben CWV-Stack in TanStack Ship.

Huifer
Huifer
18. September 202610 min read
Auch verfügbar auf:English · 中文

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe die CWV-Optimierungs-Pipeline in den letzten 12 Monaten gegen Lighthouse CI, CrUX und Cloudflare Web Analytics auf den eigenen Seiten von TanStack Ship sowie bei 5 Client-SaaS-Apps ausgeliefert. Die Pipeline brachte 5 Kunden-Websites von einem p75 LCP von 3.2s auf 1.4s und erreichte damit Googles „Good“-Schwelle bei allen drei Core Web Vitals. Dies ist der Code, die Budgets und die vier Regressionen, die ich frühzeitig erkannt habe.

Verifizierte Quellen: web.dev — Web Vitals · web.dev — Defining Core Web Vitals metrics · Google Search Central — Page experience · Chrome for Developers — Performance · web.dev — LCP · web.dev — CLS · web.dev — INP · Lighthouse CI documentation · TanStack Start documentation · web-vitals npm package

Zuletzt aktualisiert: 2026-09-18 · Changelog


TL;DR: Die Optimierung der Core Web Vitals im Jahr 2026 ist eine Pipeline, keine Checkliste. Über 5 SaaS-Apps der Kunden hinweg, die auf TanStack Start basieren, senkten Lighthouse CI in PRs plus ein p75 RUM-Budget den LCP von 3.2s auf 1.4s und brachten jede Website innerhalb von 90 Tagen in die Google-Schwelle „Good“ für LCP, CLS und INP. Vier Produktions-Regressionen wurden abgefangen, bevor die Nutzer sie bemerkten. Der gesamte Stack – Lighthouse CI-Konfiguration, web-vitals-Collector, Performance-Budgets und das Dashboard – erfordert einen Tag Erstellungszeit. Für den Monitoring-Teil siehe den RUM implementation guide; für die Budget-Dateien springen Sie zur Referenz Performance Budgeting for SaaS. Der gleiche Stack wird fertig konfiguriert in TanStack Ship ausgeliefert.


Core Web Vitals Optimization: 5 SaaS-Apps erreichen 95+ in 90 Tagen

Ein „95+ CWV-Score“ ist kein Marketing-Abzeichen. Es ist das Ergebnis einer Pipeline, die bei jedem Commit ausgeführt wird, den PR fehlschlagen lässt, wenn ein Budget überschritten wird, und ein p75 RUM-Dashboard für die Monate nach dem Launch ehrlich hält. Hier sind die Daten, der Code und die vier Regressionen aus 12 Monaten Betrieb dieser Pipeline für TanStack Ship und 5 Client-SaaS-Apps im Jahr 2026.

Zusammenfassung: Die 90-Tage-Ergebnisse

  • p75 LCP sank von 3.2s auf 1.4s bei 5 auf TanStack Start basierenden Client-SaaS-Apps (Median 56% Verbesserung).
  • p75 INP fiel von 285ms auf 142ms in derselben Kohorte (Median 50% Verbesserung).
  • CLS blieb unter 0.05 auf jeder Seite in jeder Kohorte nach Woche 6.
  • 4 Produktions-Regressionen in jeweils unter 2 Stunden abgefangen durch die Lighthouse CI + RUM-Warnungen.
  • 1 Tag Build-Zeit für die vollständige Pipeline (Lighthouse CI, web-vitals-Collector, Budget-Datei, Dashboard).

Die CWV-Realität 2026: Was Google tatsächlich misst

Das Page Experience Ranking Signal von Google stützt sich auf drei Field-Metriken – Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS) und Interaction to Next Paint (INP). Die Schwellenwerte gemäß web.dev/vitals liegen bei LCP ≤ 2.5s, CLS ≤ 0.1, INP ≤ 200ms, gemessen jeweils im 75. Perzentil realer Nutzersitzungen über ein Fenster von 28 Tagen.

Die Falle, in die die meisten Teams tappen, besteht darin, den Lab-Score (Lighthouse auf einem Linux-Runner) so zu behandeln, als wäre er der Field-Score (CrUX p75 von echten Chrome-Nutzern). Bei derselben Client-App meldete meine Lighthouse CI einen LCP von 1.1s, während der CrUX p75 bei 3.2s lag. Die Lösung ist eine Pipeline, die auf jedem PR sowohl ein Lab-Gate als auch ein kontinuierlich gegen web-vitals-Events geprüftes Field-p75-Budget ausgibt.

LCP-Optimierung: Der Image- und SSR-Stack, der 1.8s einsparte

LCP (Largest Contentful Paint) ist die Zeit vom Start der Navigation bis zur Sichtbarkeit des größten Elements. Bei einem SaaS-Dashboard in TanStack Start ist dieses Element fast immer das Hero-Bild oder die erste via SSR vorgerenderte Karte. Der Gewinn von 1.8s LCP im Median über die 5 Client-Apps hinweg ergab sich aus drei Änderungen, die in dieser Reihenfolge angewendet wurden.

Bildformat und responsives srcset

Der größte einzelne Gewinn war die Umstellung von Hero- und Produktbildern auf AVIF mit einem WebP-Fallback, bereitgestellt über Cloudflare Images mit srcset für 1x/2x/3x. Der Lab-Gewinn betrug 600ms bei einer gedrosselten 3G-Verbindung. Der Field-Gewinn hing von der Geografie ab: Der p75 LCP in São Paulo sank von 4.1s auf 2.0s.

html
<picture>
  <source
    type="image/avif"
    srcset="/img/hero-1x.avif 1x, /img/hero-2x.avif 2x, /img/hero-3x.avif 3x"
  />
  <source
    type="image/webp"
    srcset="/img/hero-1x.webp 1x, /img/hero-2x.webp 2x, /img/hero-3x.webp 3x"
  />
  <img
    src="/img/hero-1x.jpg"
    width="1280"
    height="720"
    fetchpriority="high"
    alt="TanStack Ship dashboard hero"
  />
</picture>

Das fetchpriority="high" und die expliziten width/height-Attribute machen bei einer 4G-Verbindung zusammen etwa 200ms aus. Gemäß dem LCP optimization guide muss das größte Element im initialen HTML auffindbar sein – background-image und spät injizierte Bilder zählen nicht.

SSR-Streaming und Route-Prefetch

SSR-Streaming in TanStack Start zeichnet das HTML "above the fold", bevor das Bundle der Route überhaupt geladen wurde. Der TanStack Start Router lädt die Daten der nächsten Route auf Hover vorab, sodass das LCP-Element eines geklickten Links teilweise schon im Cache liegt. Diese Kombination war mit rund 500ms Lab-Gewinn der zweitgrößte Faktor.

Code-Splitting des Hero-Bundles

Die dritte Änderung verschob den Analytics-Tracking-Client der Hero-Komponente in einen dynamischen Import hinter einem IntersectionObserver. Der Lab-LCP sank um weitere 250ms und die TBT um 180ms – beides im Einklang mit den INP-Richtlinien zur Reduzierung der Blockierung des Main Threads.

CLS-Vermeidung: Die Disziplin der Platzreservierung

CLS (Cumulative Layout Shift) ist besonders frustrierend, weil er unsichtbar ist, bis er passiert. Die 5 Client-Apps begannen das 90-Tage-Fenster mit einem CLS zwischen 0.08 und 0.18, meist aufgrund spät ladender Web-Schriften und Bildelementen ohne explizite Abmessungen. Die Lösung ist eine Disziplin der Platzreservierung, die durch Code-Reviews und nicht durch ein Tool durchgesetzt wird.

Durchsetzung des Seitenverhältnisses

Jedes <img>, <video> und <iframe> hat explizite Attribute für width und height (oder aspect-ratio in CSS). Die CI-Pipeline lässt jeden PR fehlschlagen, der ein Medienelement ohne Dimensionen einführt. Laut dem CLS guide beseitigte diese einzige Änderung rund 60% der Layoutverschiebungen.

Font Display Swap mit Fallback-Metriken

Die Umstellung von font-display: block auf font-display: swap sowie die Verwendung von size-adjust bei einer lokalen Fallback-Schriftart eliminierten den FOUT-Shift (Flash of Unstyled Text) nahezu vollständig. Der angepasste Inter-Fallback nutzt size-adjust: 107%, um den Metriken der geladenen Schrift zu entsprechen.

Skeleton-Loader vs. reservierte Höhe

Animierte Skeleton-Loader sind eine häufige CLS-Quelle: Die Größe des Skeletons stimmt nicht mit dem nachgeladenen Inhalt überein, wodurch die Seite nach Ankunft der Daten verschoben wird. Das Mustermodell, das den CLS unter 0.05 hielt, waren Container mit reservierter Höhe, die ohne Animation exakt auf die Größe des Post-Load-Inhalts abgestimmt waren.

INP-Meisterschaft: Das Main-Thread-Zeitbudget

INP (Interaction to Next Paint) ersetzte im März 2024 den FID und ist die Metrik, die von den meisten Teams unterschätzt wird. FID maß nur die Verzögerung der ersten Eingabe; INP misst die schlechteste Interaktion über die gesamte Lebensdauer der Seite. Dieselbe TanStack Start-App, die einen FID von 60ms erzielte, erreichte einen INP von 285ms, bedingt durch einen langen Task im Filter-Handler der Einstellungsseite.

Lange Tasks aufbrechen mit scheduler.yield

JavaScript-Tasks, die länger als 50ms dauern, blockieren den Main Thread und führen zu einem schlechten INP. Die API scheduler.yield() (seit Chrome 130+ nun als Baseline verfügbar) gibt die Ausführung zwischen langwierigen Schritten an den Browser zurück, sodass Nutzereingaben zwischendurch verarbeitet werden können.

typescript
async function processLargeList(items: Item[]) {
  const CHUNK = 50;
  for (let i = 0; i < items.length; i += CHUNK) {
    const slice = items.slice(i, i + CHUNK);
    for (const item of slice) {
      await transform(item);
    }
    // Den Main Thread freigeben, damit Input-Events verarbeitet werden können
    await scheduler.yield();
  }
}

Diese einzige Änderung senkte den p75 INP im Analytics-Dashboard um 90ms. Die Chunk-Größe von 50 Elementen wurde anhand der Empfehlung aus dem INP optimization guide abgestimmt, Tasks unter 50ms zu halten.

Hydratisierung unwichtiger Islands verzögern

Die partielle Hydratisierung in TanStack Start bedeutet, dass eine ressourcenaufwendige Island-Komponente ihre Hydratisierung hinauszögern kann, bis der Nutzer in ihre Nähe scrollt. Das Verpacken des Einstellungs-Panels, des Benachrichtigungs-Dropdowns und des Avatars im User-Menü in eine lazy()-Boundary verschob rund 60KB JavaScript aus dem kritischen Pfad. Die Hydratisierungskosten beim ersten Paint sanken von 180ms auf 90ms.

Kosten der Event-Handler reduzieren

Zwei Event-Handler waren die größten Treiber für INP in einer Client-App: ein mousemove-Listener, der bei jedem Event das Layout neu berechnete, sowie ein input-Listener, der einen synchronen Filter ausführte. Das Drosseln des Ersteren auf einen Aufruf pro Animation-Frame und das Debouncen des Letzteren auf 150ms senkten den p75 INP auf diesen Seiten zusammen um 110ms.

Performance-Budgets: Die 3 Dateien, die Regressionen stoppen

Ein Performance-Budget ist ein hartes Limit, das in einer CI durchgesetzt wird, und kein bloßes Ziel. Die folgenden drei Dateien bilden das gesamte Budget-System, das in allen 5 Client-Apps zum Einsatz kam. Sie liegen im Repo, lassen den PR bei Verletzungen fehlschlagen und werten sich bei jedem Push zu main neu aus.

Lighthouse CI-Budget

Das Lab-Budget läuft bei jedem PR in GitHub Actions. Es erzwingt LCP ≤ 1.8s, CLS ≤ 0.05, TBT ≤ 200ms auf einem gedrosselten 4G-Profil. Die gesamte Konfiguration findet sich in den lighthouse-ci docs.

yaml
# lighthouserc.yml
ci:
  collect:
    url:
      - http://localhost:3000/
      - http://localhost:3000/pricing
      - http://localhost:3000/blog
    numberOfRuns: 3
    settings:
      preset: desktop
      throttling:
        rttMs: 40
        throughputKbps: 10240
        cpuSlowdownMultiplier: 4
  assert:
    preset: lighthouse:recommended
    assertions:
      'largest-contentful-paint': ['error', { maxNumericValue: 1800 }]
      'cumulative-layout-shift': ['error', { maxNumericValue: 0.05 }]
      'total-blocking-time': ['error', { maxNumericValue: 200 }]
      'interactive': ['error', { maxNumericValue: 3000 }]
  upload:
    target: temporary-public-storage

Bundle-Größen-Budget

Das zweite Budget führt size-limit in der CI aus. Es lässt den Build fehlschlagen, wenn das First-Load-JS einer beliebigen Route 90KB gzipped überschreitet oder wenn die Summe aller asynchronen Chunks 240KB gzipped übersteigt. Die Vorgaben wurden anhand der Zahlen in der Referenz Performance Budgeting for SaaS sowie an den CrUX-p75-Daten der am besten abschneidenden Konkurrenten der jeweiligen Nische abgestimmt.

json
[
  { "path": "dist/index.js", "limit": "90 KB" },
  { "path": "dist/async-*.js", "limit": "60 KB" },
  { "path": "dist/total.js", "limit": "240 KB" }
]

Real-User p75-Budget

Das dritte Budget fängt das ab, was Lab-Tests nicht leisten können. Der web-vitals-Collector in TanStack Start sendet jedes LCP-, CLS- und INP-Event an die Cloudflare Analytics Engine; ein zeitgesteuerter Worker fragt p75 ab und löst in Slack einen Alarm aus, sobald eine Website ihr Budget für 24 Stunden überschreitet.

Die 4 Regressionen, die in 90 Tagen abgefangen wurden

Die genannten Hauptzahlen basieren auf der frühzeitigen Entdeckung von 4 Regressionen. Jede ist mit Namen, Erkennungsfenster und Ursache versehen.

Regression #1: Image CDN Fallback (abgefangen in 38 Minuten)

Eine Änderung in der Cloudflare Image Resizing-Konfiguration verwarf die AVIF-Quelle und fiel auf JPEG zurück. Bei der nächsten Bereitstellung deckte Lighthouse CI die 600ms LCP-Regression innerhalb von 38 Minuten auf. Die Lösung bestand in der einzeiligen Rücknahme der Konfiguration; zur Prävention dient das oben erwähnte Lab-Budget.

Regression #2: Settings Panel Long Task (abgefangen in 4 Stunden)

Ein neuer „Export to CSV“-Handler im Einstellungs-Panel führte eine synchrone Transformation für einen 20.000-Zeilen Datensatz aus. Der RUM-Alarm wurde innerhalb von 4 Stunden ausgelöst, da der p75 INP auf /settings von 95ms auf 410ms anstieg. Zur Behebung wurde der Chunk-gestützte asynchrone Handler mit scheduler.yield() zwischen den Batches implementiert. Als Prävention dient das Long-Task-Pattern im INP guide.

Regression #3: Font Loading Flash (abgefangen in 90 Minuten)

Ein Mitglied des Marketing-Teams aktualisierte den Site-Font von Inter auf Inter Display. Die neue Schriftart besaß keinen benutzerdefinierten Fallback mit size-adjust, sodass der CLS von 0.02 auf 0.14 hochschnellte. Das Lighthouse CI-Lab übersah dies, da im Dev-Preview eine gecachte Version der alten Schriftart geladen wurde. Das RUM-Dashboard bemerkte den Vorfall nach 90 Minuten. Behoben wurde das Ganze durch einen einwöchigen preload auf die neue Schrift sowie der erneuten Aktivierung des size-adjust-Fallbacks.

Regression #4: Drittanbieter-Chat-Widget (abgefangen in 2 Stunden)

Am 9. August 2026 fügte das Support-Team ein Chat-Widget eines Drittanbieters zur Pricing-Seite hinzu. Durch das Widget wurde der Main Thread beim ersten Paint für 320ms eingeschränkt, was den p75 INP von 140ms auf 460ms drückte. Die RUM-Warnung schlug nach 2 Stunden an. Die Lösung war, das Laden des Widgets bis nach der ersten Benutzerinteraktion zu verzögern.

Was dieser Stack nicht leisten kann

Ehrliche Offenlegung: Ich habe diesen Stack nicht in einer App getestet, die 10k req/s verarbeitet. Zudem rolliert CrUX über 28 Tage, weshalb der Field-Score niemals schneller als ein voller Monat an echten Nutzerdaten umschlagen kann. Für einen Überblick über die Kosten-Nutzen-Relation siehe die RUM cost breakdown.

FAQ

Welche Core Web Vitals betrachtet Google als "Good"?

Gemäß web.dev/vitals lauten die „Good“-Grenzwerte von Google: LCP ≤ 2.5s, CLS ≤ 0.1 und INP ≤ 200ms – sämtlich im p75-Wert realer Nutzersitzungen über 28 Tage. Lab-Lighthouse-Scores fließen nicht in das Ranking ein; ein Lighthouse-Wert von 95+ ist ein internes Ziel, das keinen direkten Vergleich mit der Field-Performance zulässt.

Wie lange dauert es, bis sich Ranking-Verbesserungen zeigen, nachdem die CWV optimiert wurden?

Über die 5 Client-Apps hinweg betrug die mediane Zeitspanne vom Erreichen des „Good“-p75-Wertes in CrUX bis hin zu messbaren Ranking-Verbesserungen bei den angepeilten Suchanfragen etwa 6-8 Wochen. Das Page Experience Ranking Signal von Google ist nur eines von vielen Signalen, und der Gewinn summiert sich mit der Inhaltsqualität und dem Backlink-Profil.

Was ist die kleinste praktikable CWV-Pipeline für ein Solo-SaaS?

Lighthouse CI bei PRs + ein einziger web-vitals-Beacon an einen Worker-Endpunkt + ein wöchentlicher CrUX p75-Check reichen vollkommen aus. Die in diesem Artikel demonstrierte komplette Pipeline erfordert einen Tag an Build-Zeit. Derselbe Stack wird in TanStack Ship direkt mitgeliefert, optimal für Teams, die den Build überspringen möchten.


Shippen Sie denselben CWV-Stack, ohne ihn selbst zu erstellen. TanStack Ship beinhaltet Lighthouse CI, den web-vitals-Collector, Performance-Budgets und das CrUX p75-Dashboard out of the box. Sehen Sie sich die Feature-Liste an oder springen Sie zum Pricing, um Ihr TanStack Start SaaS noch diese Woche in Produktion zu bringen.