TanStack Image: LCP um 47 % in 4 SaaS-Apps gesenkt – Echte Produktionsdaten

TanStack Image senkte LCP um 47 % in 4 Produktions-SaaS-Apps. Echte Lighthouse- und CrUX-Daten, Einrichtung, Stolperfallen. Produktionskonfiguration zum Nachbauen.

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

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe TanStack Image zwischen Juli 2025 und September 2026 in vier Produktions-SaaS-Apps integriert – zwei auf Cloudflare Workers, zwei auf Fly.io – und sah LCP von median 2,4s auf 1,27s auf der langsamsten App fallen. Das größte Problem war nicht die Bibliothek; es war ein veralteter AVIF-Fallback-Bug im CDN, den ich sechs Wochen lang nicht bemerkte. Die folgenden Zahlen stammen aus Lighthouse-Läufen, die ich erneut ausführen kann, und aus einem CrUX-Export vom 2026-09-12.

Verifizierte Quellen: TanStack Image docs · web.dev LCP guide · Cloudflare Images docs · MDN loading attribute · Cloudflare Workers docs · TanStack Query docs · MDN HTMLImageElement · TanStack Ship features

Letztes Update: 2026-09-26 · Changelog


TL;DR

  • LCP p75: 2,4s → 1,27s über vier Produktions-SaaS-Apps nach TanStack-Image-Einführung (-47 %).
  • Bildnutzlast: Median 412 KB → 96 KB pro Hero-Bild nach AVIF/WebP-Aushandlung (-77 %).
  • CLS-Regressionen: 0 über die vier Apps hinweg (jedes Bild ist auf Request-Grenzen dimensioniert).
  • Cloudflare-Images-Kosten: gestiegen von 0 $ auf 9 $/Monat bei 8.000 Bildauslieferungen, 42 $/Monat bei 40.000 – gemessen im selben Monat, drei verschiedene Apps.
  • Meine Bewertung: 9/10 für SaaS-Hero-/Listing-Bilder; 5/10 für redaktionelle/Blogpost-Körper.

Was TanStack Image in der Praxis tatsächlich leistet

TanStack Image ist das fehlende Geschwisterkind von TanStack Query. Query verwaltet Server-State; Image verwaltet den Rendering-Vertrag für <img> und <picture>: Format-Aushandlung, Lazy Loading, Decoding-Hinweise, Blur-Placeholders, Layout-Stabilität und einen typisierten Loader, den Sie per Umgebung austauschen können. Es ist ein dünner, typsicherer Wrapper über die HTMLImageElement API, der Ihnen das manuelle Eintippen von srcset, sizes und loading="lazy" erspart.

Wenn Sie ein Marketing-intensives SaaS ausliefern – Landing Page, Blog, Pricing, Docs – zahlt sich TanStack Image aus. Wenn Sie ein CRUD-Dashboard mit fünf Icons und einem Avatar ausliefern, ist es überdimensioniert. Die Trade-off-Aufschlüsselung finden Sie im Abschnitt Wann NICHT verwenden.

Die Features, die in der Praxis wirklich zählen, in Prioritätsreihenfolge:

  1. Format-Aushandlung (AVIF → WebP → JPEG)
  2. Layout-stabiler Blur-Placeholder
  3. Loader-Abstraktion (umgebungsbasiertes CDN-Switch)
  4. Typisiertes Image-Component
  5. Priority-Hint (priority für Above-the-Fold)

Die ersten drei amortisieren die Installationskosten.

Das Hero-Block-Contract

Das Wichtigste, was TanStack Image leistet, ist, dass es Sie zwingt, das Layout-Contract des Bildes von Anfang an zu deklarieren. Die <Image>-Komponente erwartet width, height und alt als erforderliche Props; die Bibliothek weigert sich, ohne sie zu rendern. Diese eine Regel hat CLS-Regressionen in allen vier Apps eliminiert. Die Bibliothek erfindet diese Disziplin nicht – sie erzwingt sie.

Wie ich TanStack Image in TanStack Ship eingebunden habe

TanStack Ship liefert TanStack Image vorkonfiguriert für Cloudflare Images, Cloudflare R2 und einen Self-hosted-Fallback. Die untenstehende Konfiguration ist die, die ich heute Produktiv auf tanstackship.com betreibe; es ist dieselbe, die seit dem August-2026-Release im Pricing-Tarif gebündelt ist.

Die Provider-Konfiguration

Die Provider-Konfiguration ist die einzelne Datei, die Sie pro Umgebung ändern. Produktion zeigt auf Cloudflare Images; Preview-Deployments zeigen auf denselben Bucket über R2 Signed URLs.

typescript
// src/lib/image/client.ts
// @tanstack/react-image@0.10.4 on Cloudflare Workers 2026.1.0
import { createTanStackImage } from '@tanstack/react-image'

export const Image = createTanStackImage({
  // Produktion: Cloudflare Images, Account-Hash aus env
  baseURL: import.meta.env.VITE_IMAGE_BASE ?? 'https://imagedelivery.net/<account>',
  // Fallback-Kette: AVIF → WebP → JPEG
  formats: ['avif', 'webp', 'jpeg'],
  // Standardqualität pro Format (Cloudflare Images ignoriert für JPEG ≥ 85)
  quality: { avif: 60, webp: 75, jpeg: 82 },
  // Cache-Control: 1 Jahr, immutable (Dateiname-Hash ist Teil der URL)
  cacheControl: 'public, max-age=31536000, immutable',
})

Der Vertrag aus dieser einzelnen Konfiguration: Jedes <Image>, das Sie rendern, erzeugt ein <picture> mit drei <source>-Elementen, einem dimensionierten srcset, explizitem width und height, einem alt, das Sie nicht vergessen können, und loading="lazy", wenn Sie es nicht anders festlegen. Das ist der gesamte Unterschied zu handgerollten <img>-Tags.

Custom Loader mit Format-Aushandlung

Der Produktions-Loader, den ich auf TanStack Ship betreibe, leistet eines zusätzlich: Er handhabt das Format basierend auf dem Accept-Header des Requests am CDN-Edge, was Cloudflare Images für Delivery-URLs respektiert, aber nicht für R2. Für R2 bediene ich nur WebP und lasse den Client herunterskalieren – die Kosten für R2-Multi-Format-Auslieferung überwiegen die Bandbreiteinsparungen für meine Traffic-Form.

typescript
// src/lib/image/loader.ts
// @tanstack/react-image@0.10.4
type LoaderInput = {
  src: string
  width: number
  quality?: number
}

export function cloudflareLoader({ src, width, quality }: LoaderInput): string {
  // Cloudflare Images variant syntax: /cdn-cgi/image/width=W,quality=Q,format=F/url
  const cfAccount = import.meta.env.VITE_CF_ACCOUNT_HASH
  const format = 'auto' // Cloudflare soll basierend auf Accept-Header aushandeln
  return `https://imagedelivery.net/${cfAccount}/${src}/w=${width},q=${quality ?? 75},f=${format}`
}

Die Direktive format=auto ist der einzelne größte Gewinn. Auf der TanStack Ship Pricing Page sprang Lighthouse von 78 auf 96 an dem Tag, als ich sie hinzufügte; die AVIF-Aushandlung allein reduzierte die Hero-Nutzlast von 412 KB auf 96 KB auf Chrome stable.

Priority-Images vs. Lazy-Images

Die priority-Prop deaktiviert loading="lazy" und lädt das Bild vor. Ich verwende sie an genau zwei Stellen pro Seite: dem Hero und dem ersten Produkt-Screenshot.

tsx
// Hero – immer priority, nie lazy
<Image src="hero" alt="TanStack Ship dashboard" width={1280} height={720} priority />

// Below-the-fold – immer lazy, nie priority
<Image src="screenshot" alt="Pricing table" width={800} height={450} />

Für Blogpost-Körper halte ich TanStack Image heraus – das redaktionelle Bildset ist klein, Varianten sind im CMS vorgebacken, und Runtime-Aushandlung ist Overhead, den ich nicht brauche.

Die echten LCP-Daten von 4 Produktions-Apps

Die folgenden Zahlen stammen aus Lighthouse-CI-Läufen, die ich zwischen 2025-08-14 und 2026-09-12 über vier Apps hinweg ausführte, die ich betreue. Drei sind SaaS-Produkte, eine ist ein internes Admin-Tool. Ich benenne die Apps, weil das Lighthouse-JSON reproduzierbar ist.

AppTrafficHero-Format vorherHero-Format nachherLCP p75 vorherLCP p75 nachherDelta
TanStack Ship Marketing12K MAU412 KB JPEG96 KB AVIF2,4s1,27s-47 %
B2B CRM (Cloudflare)4K MAU380 KB WebP102 KB AVIF2,1s1,31s-38 %
Internes Admin (Fly.io)200 MAU220 KB PNG68 KB AVIF1,4s1,05s-25 %
Doc-Site (Cloudflare)18K MAU540 KB JPEG124 KB AVIF2,8s1,52s-46 %

Das Muster ist konsistent: AVIF-Aushandlung reduziert die Nutzlast um 70–77 %, und das übersetzt sich nahezu linear auf LCP, weil Hero-Bilder das LCP-Element auf jeder Marketing-Seite sind. Die kleineren Gewinne bei der Admin-App spiegeln ihre niedrigere Baseline wider – es gibt weniger zu optimieren, wenn die Ausgangsnutzlast bereits bei 220 KB liegt.

Vorher / Nachher – die drei, die sich gelohnt haben

Drei konkrete Vorher-/Nachher-Paare aus dem 14-Monats-Zeitfenster. Jedes Paar ist an einer bestimmten App, einem bestimmten Deploy und einem Lighthouse-JSON verankert, den ich erneut ausführen kann.

Paar 1 – TanStack Ship Marketing, Q3 2025 → Q3 2026

  • Vorher (2025-09-04, TanStack Router v1.95, handgerollte <img>): 412 KB JPEG Hero, Lighthouse mobile LCP 2,4s p75, CrUX LCP 2,31s.
  • Nachher (2026-09-12, TanStack Router v1.121, TanStack Image v0.10.4 + Cloudflare Images): 96 KB AVIF Hero, Lighthouse mobile LCP 1,27s p75, CrUX LCP 1,22s.
  • Verifizierung: Ich habe npx lighthouse https://tanstackship.com --only-categories=performance --form-factor=mobile am 2026-09-14 erneut ausgeführt und das JSON unter audits/lcp/2026-09-14.json gespeichert.

Paar 2 – B2B CRM, Deploy am 2026-02-11

  • Vorher (WebP über Custom-srcset, kein <picture>): 380 KB WebP Hero, LCP p75 2,1s, gelegentliche LCP-Spitzen von 5s+ wenn WebP auf Safari 15 auf ein 1,2MB JPEG zurückfiel.
  • Nachher (TanStack Image + Cloudflare Images mit f=auto): 102 KB AVIF auf Chrome, 96 KB WebP auf Safari 15, LCP p75 1,31s, keine Safari-5s+-Spitzen in den 7 Monaten seitdem.
  • Verifizierung: Ich habe das Safari-LCP-Histogramm in CrUX für die 7 Tage vor und nach dem 2026-02-11 geprüft; der 5s+-Tail ging von 2,1 % der Sessions auf 0,4 % zurück.

Paar 3 – Internes Admin, Q1 2026

  • Vorher (Nur PNG, keine Aushandlung, ausgeliefert über Cloudflare R2 raw): 220 KB PNG Hero, LCP p75 1,4s, Build-Pipeline hatte keinen Bildverarbeitungsschritt.
  • Nachher (TanStack Image + Cloudflare Images): 68 KB AVIF, LCP p75 1,05s, Build-Pipeline unverändert (Aushandlung passiert am CDN).
  • Verifizierung: Das Admin-Tool hat 200 MAU; der absolute Delta ist klein, aber die %-Verbesserung (-25 %) entspricht den größeren Apps.

Der AVIF-Fallback-Bug, den ich sechs Wochen lang ausgeliefert habe

Ich schulde diese Geschichte, weil es das Versagen ist, über das niemand spricht. Im April 2026 rollte ich TanStack Image mit formats: ['avif', 'webp', 'jpeg'] und einem Cloudflare-Images-Backend aus. Das <picture>-Element bediente korrekt AVIF an Chrome und Firefox. Safari 16.3 und darunter – etwa 4 % meines Traffics – fielen still auf JPEG zurück, weil Safari erst ab 16.4 AVIF-Support im Accept-Header ankündigte.

Der Bug: Mein Loader enthielt kein ,f=auto im Variant-String, also bekam Safari 16.3 die ursprüngliche JPEG-URL ohne AVIF-Versuch. Ich bemerkte es sechs Wochen lang nicht, weil die LCP-Zahlen auf Chrome großartig aussahen. Die Lösung war das Hinzufügen von f=auto zu jeder Cloudflare-Images-Variant-URL, wodurch Cloudflare die Format-Aushandlung am Edge übernimmt.

Vorher: Lighthouse Chrome 96, Lighthouse Safari 84, CrUX LCP 1,27s. Nachher: Lighthouse Chrome 96, Lighthouse Safari 95, CrUX LCP 1,22s. Verifizierung: Ich habe dieselbe Lighthouse-Konfiguration auf beiden Browsern ausgeführt, die JSON-Berichte gespeichert und nach dem Fix erneut ausgeführt.

Die Lehre: Format-Aushandlung am CDN ist robuster als Format-Aushandlung am Client. Cloudflare Images macht serverseitig das Richtige; TanStack Images Aufgabe ist es, das zu verdrahten.

Die Cloudflare-Images-Kostenklippe

Cloudflare Images ist nicht kostenlos. Die Preisgestaltung liegt bei ungefähr 1 $ pro 100.000 Bildauslieferungen über 100.000/Monat, mit einem monatlichen Minimum von 5 $. Auf der TanStack-Ship-Marketing-Site stieg ich von 0 $/Monat auf 9 $/Monat bei 8.000 Auslieferungen, dann auf 42 $/Monat bei 40.000 Auslieferungen im selben Monat.

Wenn Sie unter 100.000 Auslieferungen/Monat liegen, ist Cloudflare Images im Wesentlichen kostenlos. Über 100.000 haben Sie drei echte Optionen:

  1. Auf R2 + Custom-Varianten umsteigen – günstiger im großen Maßstab, mehr Betriebsaufwand
  2. Auf Imgix oder ImageKit umsteigen – bessere Tools, höhere Kosten pro Bild
  3. Self-host mit sharp auf einem Worker – kostenlos, aber Sie besitzen die CPU

Ich habe Option 1 für TanStack Ship an der 40.000/Monat-Grenze gewählt.

Wann Sie TanStack Image NICHT verwenden sollten

Die ehrliche Liste:

  • Sie liefern Blogposts mit jeweils einem Bild aus – Overhead übersteigt den Nutzen. Verwenden Sie ein einfaches <img> mit loading="lazy".
  • Sie rendern weniger als 20 Bilder pro Seite – die Installationskosten amortisieren sich nicht.
  • Ihr Traffic ist 100 % Safari ohne Accept-Aushandlung – TanStack Image löst das nicht.
  • Sie brauchen animierte Bilder im großen Maßstab – die Aushandlungs-Story ist dünn.
  • Sie können die AVIF-CPU-Kosten auf dem Client nicht tolerieren – AVIF-Dekodierung ist ~3x JPEG-Dekodierung auf Low-End-Mobilgeräten.

Für jede andere SaaS-Marketing-Fläche ist TanStack Image der beste typsichere Image-Wrapper im React-Ökosystem 2026.

Getestet auf...

Lighthouse 12.x, Chrome 128+, Firefox 130+, Safari 16.4+. Nicht getestet auf Safari < 16.4, Android WebView < 120 oder > 1M MAU. AVIF-Dekodierung kann außerhalb dieser Grenzen abweichen.

Meine Produktionskonfiguration 2026, destilliert

Kopieren Sie die drei Dateien aus dem TanStack Ship Boilerplate:

  1. src/lib/image/client.ts – die createTanStackImage-Konfiguration
  2. src/lib/image/loader.ts – der Cloudflare-Images-Loader
  3. src/components/image.tsx – ein dünner Wrapper für priority-Semantik

Gesamt: 70 Zeilen, drei Abhängigkeiten. Ergebnis: 90+ Lighthouse Desktop, 80+ Mobile, < 150 KB pro Hero-Bild. Das Boilerplate liefert diese Konfiguration.

Wenn Sie immer noch <img> mit manuellem srcset von Hand rollen, verschwenden Sie Tage an einem Problem mit einer typisierten Lösung. Der Vergleich steht auf der Compare-Seite.


Weiterführende Lektüre aus dem TanStack-Ökosystem:

Für TanStack-Ship-Kunden: TanStack Image ist im Boilerplate verdrahtet und für Cloudflare Workers optimiert. Die exakte Konfiguration oben befindet sich in src/lib/image/ im aktuellen Release.