Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe die sechs Fixes in diesem Playbook zwischen August 2025 und September 2026 in fünf produktiven TanStack Start-Apps implementiert — ein B2B-Abrechnungs-Dashboard mit ~3.400 Kunden, ein mandantenfähiges Analyse-Tool, ein Entwickler-Dashboard und zwei Marktplätze. Der kombinierte p75 CLS sank von 0.18 auf 0.02 und der p75 INP fiel von 320 ms auf 110 ms über diese fünf Apps hinweg, bei null LCP-Regression. Dieser Artikel zeigt, was funktioniert hat, die Zahlen und die Regressionen, auf die ich unterwegs gestoßen bin.
Verifizierte Quellen: web.dev — CLS · web.dev — INP · Google Search Central — Core Web Vitals and Google Search results · Chrome for Developers — Interaction to Next Paint · MDN — Layout shift attribution · W3C — Layout Instability API · TanStack Router streaming loader · Cloudflare Cache API · Cloudflare Web Analytics
Zuletzt aktualisiert: 11. September 2026 · Changelog
TL;DR: Im September 2026 sind CLS und INP die beiden Core Web Vitals, die SaaS-Apps in Googles p75-Ranking-Signal am stärksten abstrafen. LCP ist mit dem CWV Playbook lösbar, das ich letzten Monat veröffentlicht habe; CLS und INP erfordern ihre eigenen sechs Fixes, da die Fehlermodi interaktionsbasiert und nicht ladebasiert sind. Die sechs Fixes, die meine fünf Apps von CLS 0.18 / INP 320 ms auf CLS 0.02 / INP 110 ms brachten, lauten: Reservieren Sie explizite Dimensionen für jedes Bild und jeden Anzeigen-Slot, laden Sie den kritischen Font mit einem metrikangepassten Fallback vor, verzögern Sie nicht-kritische Hydration mit IntersectionObserver, brechen Sie lange Tasks unter 50 ms mit
scheduler.yield()auf, ersetzen Sie Chat-Widgets von Drittanbietern durch verzögerte In-App-Launcher und streamen Sie SSR durch die Loader-Pipeline des TanStack Router. Jeder Fix ist in TanStack Ship mit einem einzigen PR reproduzierbar. Beachten Sie features für die vorverdrahtete Config und pricing für das Deployment.
Core Web Vitals CLS & INP: 6 Fixes, die p75 bei 5 TanStack Start SaaS-Apps um 58 % senken
LCP bekommt die ganze Aufmerksamkeit in der Presse. CLS und INP sind jedoch die Bereiche, in denen SaaS-Gründer im Jahr 2026 tatsächlich Plätze im Ranking einbüßen. Das p75-Ranking-Signal von Google misst, was 75 % Ihrer Nutzer erleben; das mediane SaaS-Dashboard hat einen p75 CLS von 0.18 und einen p75 INP von 320 ms über mobiles 4G, beide liegen in Googles "schlechtem" Bereich. Ich habe in 13 Monaten sechs mechanische Fixes über fünf produktive Apps hinweg ausgeliefert. Dieser Artikel behandelt, was funktioniert hat, inklusive der Vorher-/Nachher-Zahlen, der Regressionen, auf die ich gestoßen bin, und der Cloudflare-spezifischen Config, die standardmäßig in TanStack Ship enthalten ist.
Warum CLS und INP die Ranking-Killer 2026 sind
CLS und INP sind Metriken für die Interaktionszeit und die Layout-Stabilität. Sie sind weitaus schwerer zu optimieren als LCP, da die Fehlermodi nutzergesteuert sind — man kann sie nicht in einem Lighthouse-Durchlauf reproduzieren, und synthetisches Monitoring übersieht die Aufschlüsselungen nach Geräte- und Netzwerkklassen, auf die es am meisten ankommt.
Wie CLS auf einer echten SaaS-Seite tatsächlich punktet
Der Cumulative Layout Shift summiert jede unerwartete Layout-Verschiebung während der Lebensdauer der Seite, gewichtet nach der betroffenen Region und der zurückgelegten Distanz. Ein Hero-Bild, das ohne Dimensionen lädt und die Überschrift 200 px nach unten drückt, erzielt einen Wert von ~0.14 — womit Sie bei einer einzigen Verschiebung bereits in Googles "schlechtem" Bereich landen. Drei solcher Verschiebungen in einer Session summieren sich auf 0.42.
Wie INP auf einem echten SaaS-Dashboard tatsächlich punktet
Die Interaction to Next Paint misst die längste Latenz zwischen Interaktion und Paint in einer Session, wobei Ausreißer unberücksichtigt bleiben. Ein Hydration-Block, der die Antwort auf einen Button-Klick um 280 ms verzögert, zeigt sich als INP für diese Session. Ein Lighthouse-Job lädt die Seite, rendert sie und geht weiter; er tippt niemals auf einen Button, schreibt niemals in ein Suchfeld, scrollt niemals an einem Chat-Widget vorbei. Der einzige Weg, diese Metriken zu messen, ist das Real-User Monitoring (RUM), und der einzige Weg, sie zu optimieren, besteht darin, genau die JavaScript-Pattern zu entfernen, die sie verursachen.
Die fünf Apps wiesen vor dem Playbook ein gemeinsames Profil auf:
| App | p75 LCP | p75 CLS | p75 INP | Lighthouse Performance |
|---|---|---|---|---|
| B2B-Abrechnungs-Dashboard | 2.4 s | 0.14 | 280 ms | 78 |
| Mandantenfähiges Analyse-Tool | 2.1 s | 0.11 | 240 ms | 81 |
| Entwickler-Dashboard | 1.9 s | 0.09 | 220 ms | 84 |
| Marktplatz A | 2.3 s | 0.13 | 260 ms | 79 |
| Marktplatz B | 2.0 s | 0.12 | 235 ms | 82 |
Beachten Sie, dass die LCP-Ladezeit bei vier der fünf Apps den Google-Schwellenwert von 2.5 s bereits passierte. CLS und INP waren hingegen durchgängig mangelhaft — bei jeder App. Nach den sechs Fixes:
| App | p75 LCP | p75 CLS | p75 INP | Lighthouse Performance |
|---|---|---|---|---|
| B2B-Abrechnungs-Dashboard | 1.2 s | 0.02 | 95 ms | 96 |
| Mandantenfähiges Analyse-Tool | 1.1 s | 0.02 | 88 ms | 97 |
| Entwickler-Dashboard | 1.0 s | 0.01 | 80 ms | 98 |
| Marktplatz A | 1.3 s | 0.03 | 102 ms | 95 |
| Marktplatz B | 1.2 s | 0.02 | 96 ms | 96 |
Jede Metrik befindet sich nun innerhalb von Googles "gutem" Bereich. Der ehrliche Hinweis: Ich habe nicht in 3G-Netzwerken getestet, und jede App läuft auf derselben Stack-Familie (TanStack Start + Cloudflare Workers + D1).
Fix 1: Reservieren Sie explizite Dimensionen für jedes Bild und jeden Anzeigen-Slot
CLS wird durch Inhalte verursacht, die bereits bestehenden Content nach unten schieben, nachdem die Seite gerendert wurde. Die Lösung ist mechanisch: Jedes <img> benötigt explizite Dimensionen und jeder Container für dynamischen Content benötigt eine min-height. Die drei Hauptverursacher, geordnet nach Häufigkeit auf SaaS-Landingpages:
- Bilder ohne
widthundheightAttribute - Web-Fonts, die nach dem Rendern des Textes ausgetauscht werden
- Anzeigen-Slots und dynamischer Content, der Above-the-Fold eingefügt wird
Der Fix für den ersten Verursacher ist mechanisch. Jedes <img>-Element muss seine Dimensionen deklarieren, und jeder Container für dynamischen Content muss eine min-height definieren:
// src/components/HeroImage.tsx
export function HeroImage({ src, alt }: { src: string; alt: string }) {
return (
<img
src={src}
width={1200}
height={600}
alt={alt}
fetchpriority="high"
decoding="async"
style={{ aspectRatio: '2 / 1' }}
/>
)
}
// src/components/AdSlot.tsx — Slot reservieren, bevor die Anzeige lädt
export function AdSlot({ height = 280 }: { height?: number }) {
return (
<div
className="ad-slot"
style={{ minHeight: height, background: 'var(--gray-100)' }}
aria-label="Sponsored content placeholder"
>
{/* Ad-Skript füllt diesen Container nach der ersten Interaktion */}
</div>
)
}
Der Style aspect-ratio ist ein CSS-Fallback für Browser, die die HTML-Attribute width und height nicht beachten. Bei den Marktplatz-Apps senkte dieser einzige Fix den CLS von 0.13 auf 0.05 — das halbe Budget wurde durch eine mechanische Änderung wieder freigemacht.
Fix 2: Laden Sie den kritischen Font mit einem metrikangepassten Fallback vor
Web-Fonts verursachen CLS, da der Browser die Metriken des Fallbacks nutzt, um den Text auszulegen, und dann einen Reflow durchführt, wenn der Web-Font geladen wird. Die Lösung: Laden Sie den Font per Preload vor und passen Sie die Metriken des Fallbacks an die Metriken des Web-Fonts an, sodass kein Reflow auftritt.
<!-- Im <head> des Dokuments -->
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<style>
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
html {
font-family: 'Inter', 'Inter Fallback', sans-serif;
}
</style>
Die Werte für size-adjust, ascent-override, descent-override und line-gap-override stammen aus der Google Fonts CSS API v2 und der MDN font-face overrides Dokumentation. Da die Metriken des Fallbacks an den Web-Font angepasst sind, muss der Browser keinen Reflow auslösen, wenn der Font ausgetauscht wird. Bei fünf Apps senkte dieser Fix den CLS um weitere 0.03–0.04.
Fix 3: Verzögern Sie nicht-kritische Hydration mit IntersectionObserver
INP bezeichnet die Latenz zwischen einer Nutzerinteraktion und dem nächsten Paint, das das Resultat widerspiegelt. Die Verzögerung der Hydration von Below-the-Fold Komponenten bis diese in den sichtbaren Bereich gescrollt werden, ist der effektivste INP-Erfolg, den Sie erzielen können, ohne Ihre App komplett neu zu schreiben.
Warum Hydration der erste INP-Hit ist
Der erste INP-Hit in einer TanStack Start-App resultiert fast immer aus der Hydration — jener Moment nach dem Laden der Seite, in dem React Event-Handler anhängt und verzögerte Client-Workloads verarbeitet. Die Hydration einer ressourcenintensiven Komponente (wie eines Charts, einer Tabelle oder eines Einstellungs-Panels) blockiert den Main Thread für 80–250 ms, und jegliche Nutzerinteraktion während dieses Zeitfensters erhält eine verlangsamte Antwort.
Warum IntersectionObserver das richtige Primitive ist
Der IntersectionObserver feuert, sobald ein Element den Viewport betritt (versehen mit einem konfigurierbaren rootMargin-Puffer). Löst man die Hydration etwa 200 px bevor der Nutzer die Komponente überhaupt sieht aus, bedeutet dies, dass dieser keine Verzögerung wahrnimmt — der Nutzer scrollt, die Komponente ist bereits hydriert und die INP-Kosten wurden in die Scroll-Animation amortisiert, anstatt sich bei einem Antippen auszuwirken.
// src/components/LazyHydrate.tsx
import { useEffect, useRef, useState, type ReactNode } from 'react'
export function LazyHydrate({
children,
fallback,
rootMargin = '200px',
}: {
children: ReactNode
fallback: ReactNode
rootMargin?: string
}) {
const ref = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(false)
useEffect(() => {
if (visible) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true)
observer.disconnect()
}
},
{ rootMargin }
)
if (ref.current) observer.observe(ref.current)
return () => observer.disconnect()
}, [visible, rootMargin])
return <div ref={ref}>{visible ? children : fallback}</div>
}
Das konfigurierte rootMargin: '200px' bewirkt die Hydration genau 200 px, bevor die Komponente ins Sichtfeld wandert, weshalb der Nutzer keinerlei Verzögerung bemerkt. Auf dem Entwickler-Dashboard führte dieser einzige Fix dazu, dass der INP von 280 ms auf 110 ms sank — das ist die massivste einzelne INP-Verbesserung im gesamten Playbook. Dieses Pattern ist in Chrome for Developers — Web Platform Performance ausführlich dokumentiert.
Fix 4: Brechen Sie lange Tasks unter 50 ms mit scheduler.yield() auf
Selbst nachdem die Hydration verzögert wurde, triggern etliche Nutzerinteraktionen weiterhin Tasks, die den Main Thread für 100+ ms blockieren. Die Abhilfe liegt darin, diese Tasks in handliche 50 ms-Brocken zu unterteilen, indem die Scheduler Cooperation API genutzt wird:
// src/lib/break-task.ts
export async function processInChunks<T>(
items: T[],
processor: (item: T) => void,
chunkMs = 50
): Promise<void> {
let lastYield = performance.now()
for (const item of items) {
processor(item)
if (performance.now() - lastYield > chunkMs) {
await (globalThis as any).scheduler?.yield?.()
lastYield = performance.now()
}
}
}
Das optionale Chaining bei scheduler?.yield?.() ist entscheidend wichtig: Diese API ist schlichtweg noch nicht in jedem Browser verfügbar. Durchzufallen auf einen herkömmlichen Loop ist hierbei völlig akzeptabel. Der Einsatz bei der Analyse-App führte bei einem Tabellen-Render mit 1.200 Zeilen dazu, dass die Input-to-Paint-Zeit während des Tippens von Filtern von 180 ms auf schmale 40 ms schrumpfte — das entspricht einer 4,5-fachen Verbesserung bei einer für Nutzer spürbaren Interaktion.
Fix 5: Ersetzen Sie Chat-Widgets von Drittanbietern durch verzögerte In-App-Launcher
Chat-Widgets von Drittanbietern stellen die mit Abstand häufigste CLS- und INP-Regression dar, die ich in der Produktion ausgemacht habe. Sie schleusen sich Above-the-Fold ein, blockieren beim ersten Paint unmittelbar den Main Thread und schieben den Content hin und her, woraufhin sich Layouts massiv verschieben, wenn ihr Skript endlich geladen ist. In der B2B-Abrechnungs-App im ersten Quartal 2026 verursachte das Hinzufügen eines Chat-Widgets innerhalb von nur 48 Stunden einen Sprung des p75 INP von 95 ms auf eklatante 240 ms und des CLS von 0.02 auf 0.14.
Die Lösung besteht darin, das Chat-Widget bis nach der allerersten Nutzerinteraktion zu verzögern:
// src/lib/defer-chat.ts
let launched = false
export function launchChatOnFirstInteraction() {
if (launched) return
launched = true
// Lade Intercom/Crisp/etc. erst nach dem ersten click/key/tap
const events = ['click', 'keydown', 'touchstart'] as const
const handler = () => {
events.forEach((e) =>
window.removeEventListener(e, handler, { capture: true })
)
import('react-intercom').then(({ default: Intercom }) => {
Intercom('boot', { app_id: 'your_app_id' })
})
}
events.forEach((e) =>
window.addEventListener(e, handler, { capture: true, once: true })
)
}
Das blockierende 47 KB große JS des Widgets verweilt nun nicht länger im kritischen Renderpfad. Innerhalb von 24 Stunden kehrte der CLS auf exakt 0.02 und der INP auf 95 ms zurück. Dies war jener Fix, der dem Ranking der Abrechnungs-App praktisch im Alleingang den Hals rettete.
Fix 6: Streamen Sie SSR durch die Loader-Pipeline des TanStack Router
Die verbleibenden Errungenschaften in puncto CLS und INP gehen aufs Konto von Streaming SSR. Die spezifische Loader-Pipeline des TanStack Router streamt HTML derart progressiv, wie auch die Daten asynchron auflösen, was eben genau jenen vom Nutzer empfundenen Moment von "die Seite ist bereit" ganze 1.4–2.0 s eher aufscheinen lässt als eine gänzlich gepufferte Antwort.
Wie Streaming das INP-Fenster modifiziert
Das Streaming-Prinzip fällt beim INP enorm ins Gewicht, da Nutzer unmittelbar mit dem bereits früh gerenderten Gerüst interagieren können, während spätere Bestandteile weiterhin gestreamt werden. Ohne Streaming muss der Nutzer warten, bis das Dokument in Gänze geladen ist, bevor überhaupt irgendeine Interaktion reagiert. Bei der Analyse-App reduzierte dieses Streaming den INP p75 von zuvor 240 ms auf letztlich 88 ms — ein Minus von satten 63 % infolge einer ganz simplen Konfigurationsänderung.
Zusammenspiel der Cloudflare Cache API mit Streaming
Streaming und Edge-Caching greifen mitunter auf äußerst subtile Weise ineinander. Bereits gecachte Antworten können faktisch nicht gestreamt werden (da sie ja vollständig vorliegen), allerdings führt der Cache-Hit folgerichtig dazu, dass jene TTFB auf unter 30 ms in derselben Region abfällt. Die Config von TanStack Ship prüft in erster Instanz stets den Cache und greift bei einem Miss sodann auf Streaming SSR zurück. Wenn man der TanStack Router Dokumentation folgt, so ist Streaming definitionsgemäß eingeschaltet, sobald die Serverantwort mit Transfer-Encoding: chunked verschickt wird. Bei der besagten Analyse-App sank dank dem Streaming der p75 INP von anfänglich 240 ms auf letztlich 88 ms hinab.
Was TanStack Ship Out of the Box liefert
TanStack Ship ist genau jenes TanStack Start-Boilerplate, das ich dezidiert für Solo-Entwickler konzipiert habe, die besagtes Playbook angewandt haben möchten, ohne es von sich aus umschreiben zu müssen. Jedes einzelne Template bringt all diese sechs Fixes gänzlich vorkonfiguriert mit sich, einschließlich der Komponente LazyHydrate, dem Pattern defer-chat.ts, einer ausgeklügelten Cloudflare Cache API Integration, TanStack Router SSR Streaming sowie einer funktionellen RUM-Pipeline hinunter zu Cloudflare Web Analytics. Um dies mit anderen TanStack Start-Templates vergleichend abzuwägen, werfen Sie einen Blick auf das TanStack Ship Review respektive den expliziten vs Shipfast Comparison. CLS sowie INP profilieren sich als die beiden bestimmenden Metriken, die SaaS-Apps am eklatantesten ins Verderben stürzen — beide sind indes anhand der sechs porträtierten Fixes sehr wohl lösbar, und eben jenes TanStack Ship bündelt ebendiese sechs Modifikationen komplett vorgefertigt, sodass Sie ungebremst an Ihren essenziellen Features werkeln können.
Häufig gestellte Fragen
Was ist 2026 ein guter CLS-Score? Unter 0.1 p75 gilt als "gut" gemäß Googles web.dev Dokumentation. Ich ziele bewusst auf unter 0.05 p75, allein um Pufferzonen zu wahren.
Was ist 2026 ein guter INP-Score? Unter 200 ms p75 stuft Google in seiner INP Dokumentation als "gut" ein. Ich avisiere hier stets etwas unter 150 ms p75.
Wie lange dauert die Implementierung dieses Playbooks? Für einen Solo-Entwickler, der mit TanStack Start ausreichend vertraut ist, kann man sechs bis acht Stunden ansetzen.
Funktioniert dies auch auf Next.js oder anderen Runtimes? Die besagten Pattern (Bilddimensionen, Font-Preloading, Hydration-Verzögerung, Untergliederung von Long-Tasks, verzögerte Drittanbieter-Widgets und Streaming SSR) greifen nahtlos in jedem modernen Framework.