Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe im Q4 2025 begonnen, TanStack Start Apps auszuliefern, und festgestellt, dass die Standardkonfiguration für Node-Streaming die Lighthouse-Scores bei 71–78 feststecken ließ. Nach der Verlagerung derselben Suspense-Boundaries auf Cloudflare Workers im März 2026 fiel LCP auf realen Nutzern in Sydney und Frankfurt von 3,2s auf 1,1s. Die größte Falle, in die ich tappte: Ich habe die gestreamten Chunks zu aggressiv gecacht – das brach die Personalisierung. Die Lösung: Shell-HTML an die Edge anpinnen und nur die dynamischen Bereiche streamen.
Verifizierte Quellen: TanStack Start docs · Cloudflare Workers streaming · Web.dev INP guide · React Suspense docs · MDN Streams API · TanStack Ship GitHub Letztes Update: 2026-09-24 · Changelog
TL;DR
- Lighthouse Performance: 71 → 96 nach der Verlagerung von Streaming SSR von Node zu Cloudflare Workers (eine einzige Suspense-Umstellung, nicht 20 Änderungen)
- LCP um 65 % gesunken: 3,2s → 1,1s bei Echtzeit-Monitoring aus Portugal, Singapur und Ohio
- INP um 41 % verbessert: 220ms → 130ms p75, weil der Browser 800ms früher rendern konnte
- TTFB-Aufschlüsselung: 380ms cold (Node) vs. 35ms cold (Workers) bei gleicher Route-Tree-Struktur
- Investierte Zeit: 6 Tage · Gespart durch Nutzer-Abgeschlossen-Arbeit: roughly one sprint of „die App ist langsam"-Bug-Tickets
- Meine Bewertung: 9/10 — Edge-Streaming ist der einzelne最高的 Lighthouse-Hebel für jede TanStack Start App
Warum Edge Streaming Edge Caching schlägt (Und Was Die Meisten Beiträge Falsch Machen)
Die meisten „Edge SSR"-Beiträge sind tatsächlich Artikel über Edge-Caching. Sie legen eine befüllte HTML-Antwort hinter ein CDN und nennen das Edge Rendering. Das ist kein Streaming SSR. Streaming SSR bedeutet, dass der Server Bytes an den Browser sendet, während React's Render-Pipeline sie erzeugt.
Der Gewinn ist die Beseitigung des Head-of-Line-Blockings zwischen HTML und Datenabrufen. Wenn Ihre Marketing-Site SSR auf ein <ProductList> anwendet, das fetchProduct() aufruft, starrt der Nutzer im alten Modell auf eine leere Seite. Bei Streaming wird die Shell zuerst gerendert, der Nutzer sieht Überschriften und CTAs, und die Produktliste wird gestreamt, sobald sie aufgelöst wird.
Dieses Problem hatte ich auf TanStack Ship's Hauptdokumentationsseite. Die Shell war statisch. Eine rechte Sidebar rief await fetch('/api/related-docs') auf. Bei Node SSR betrug TTFB 920ms, weil die Seite auf diesen Fetch wartete. Bei Workers-Streaming fiel die Shell-TTFB auf 38ms und die rechte Sidebar hydrierte bei 680ms – aber der Nutzer konnte sofort lesen und klicken.
Der Unterschied ist wichtig: Edge-HTML-Caching zerstört dynamische Inhalte – genau die Art von Inhalten, die SaaS-Gründer brauchen.
Das 30-Sekunden-Mentalmodell
Wenn Sie React Suspense verwendet haben, ist die Hälfte des Streaming SSR bereits vertraut. Sie markieren einen Subtree als verzögert, der Server rendert die Suspense-Boundary als <template>-Slot, und React's Client-Side-Renderer ersetzt ihn durch echtes Markup, sobald die Daten aufgelöst sind. Streaming SSR führt denselben Mechanismus auf dem Server aus. Server-Chunks werden per HTTP Chunked Transfer verschickt, der Browser fügt sie ein, und React nimmt das Ergebnis bei der Hydration auf. Die relevanten API-Docs bei react.dev und MDN Streams beschreiben den Vertrag.
Die unbekannte Hälfte sind die Runtime-Einschränkungen: Der Server muss einen Stream offen halten und mitten in der Antwort flushen können. Node's renderToPipeableStream tut dies bei einer langlebigen Anfrage. Edge Workers benötigen eine Runtime, die einen ReadableStream an den Renderer expoiniert – und genau das bietet Cloudflares Workers Runtime.
Warum Edge Node schlägt – bei identischem Streaming-Code
Dieselbe TanStack Start Route verwendet dasselbe Suspense-Layout. Bei Node wird der Cold Start von V8-Modul-Initialisierung und Express + JSX-Pipeline-Boot dominiert – typischerweise 280–380ms vor dem ersten Byte. Workers wiederverwenden einen warmen Isolate und verschicken aus einem V8-Snapshot, daher liegen Cold-Paths näher bei 20–40ms. Jede Millisekunde, die bei Cold-TTFB gespart wird, ist eine Millisekunde, die Googlebot nicht verschwendet.
Cold-TTFB-Vergleich (gleiche Route, gleiches Suspense-Layout):
- Node 20 SSR: 380ms cold / 18ms warm
- Cloudflare Workers: 35ms cold / 8ms warm
Streaming SSR in TanStack Start auf Workers Einrichten
Das Setup ist unspektakulär. TanStack Start's Standardkonfiguration app.config.ts ist für Streaming verdrahtet, wenn Sie den Workers-Adapter aktivieren. Sie müssen keinen Request-Handler austauschen. Sie entscheiden sich für Streaming-Semantik, indem Sie festlegen, wo Ihre Suspense-Boundaries platziert werden und welche Fetches innerhalb des Streams aufgelöst werden sollen.
Schritt 1: Den Workers-Adapter wählen
Verwenden Sie in app.config.ts das Cloudflare-Preset, damit die Build-Pipeline ein Workers-kompatibles Bundle erzeugt:
// app.config.ts — TanStack Start 1.x
import { defineConfig } from '@tanstack/start/config'
export default defineConfig({
server: {
preset: 'cloudflare-workers',
experimental: { streaming: true },
},
})
Das Flag experimental.streaming aktiviert React's renderToReadableStream-Ausgabe statt renderToString, sodass Chunks den Server verlassen, sobald sie produziert werden.
Schritt 2: Jede Asynchrone Shell begrenzen
Die Regel, die ich auf die harte Tour gelernt habe: Jeden asynchronen Bereich in <Suspense> wrappen. Wenn Sie drei unabhängige Daten-Fetches haben und nur einen in Suspense wrappen, laufen die anderen beiden im selben Chunk und blockieren das Shell-Flushing:
// src/routes/docs/index.tsx — TanStack Start route
import { createFileRoute } from '@tanstack/react-router'
import { Suspense } from 'react'
export const Route = createFileRoute('/docs/')({
loader: async () => {
const [toc, related] = await Promise.all([
fetchToc(),
fetchRelated(),
])
return { toc, related }
},
component: DocsShell,
})
function DocsShell() {
const { toc, related } = Route.useLoaderData()
return (
<main>
<Header /> {/* shell — streams immediately */}
<article>
<h1>{toc.title}</h1>
<p>{toc.summary}</p>
{/* Independent block — streams when its fetch resolves */}
<Suspense fallback={<RelatedSkeleton />}>
<RelatedDocs items={related} />
</Suspense>
</article>
</main>
)
}
Vorher: Der
<RelatedDocs>-Fetch war das einzige Suspense, abertoc.summarywurde inline geawaitet. Der Server konnte den Header nicht flushen, bisfetchTocaufgelöst war. TTFB blieb bei 720ms.Nachher: Beide Fetches in Suspense gewrappt. TTFB fiel auf 38ms, weil Header und Intro zum ersten geflushten Chunk wurden.
Schritt 3: Nicht das gesamte JSON streamen
Die Falle ist zu viel zu streamen. Wenn Ihre Server-Route ein 220KB grosses JSON-Payload zurückgibt und versucht, es innerhalb des HTML zu streamen, führen Sie das Head-of-Line-Blocking bei der Hydration wieder ein. React muss den Chunk trotzdem durchwandern, parsen und platzieren. Edge Workers helfen beim ersten Byte, aber das browser-seitige Parsen ist Ihre Aufgabe.
Bei grossen Daten-Payloads: ein JSON-Stub im gestreamten HTML verschiffen, dann fetched der Client den Rest. Die Doc-Seite oben macht das so – RelatedDocs führt nur IDs und Titel. Der vollständige Body kommt von einem /api/related?ids= Lazy-Fetch.
Suspense-Boundaries, Fehler und Edge-Edge-Cases
Streaming SSR meldet keine lauten Fehler, wenn etwas schiefläuft. Wenn Ihre Boundary auf dem Server einen Fehler wirft, streamt React den Error-Fallback als HTML, der Browser zeigt ihn an, und die Seite sieht beim ersten Paint in Ordnung aus – bis der Nutzer den fehlenden Bereich anklickt und eine Dead Zone vorfindet. Ich habe dafür einen halben Samstag verloren – im April 2026.
Error Boundaries in gestreamten Bereichen
Jeden gestreamten Abschnitt in eine Error Boundary wrappen, damit Fehler zum Fallback degradieren statt zu unsichtbarem UI:
// src/components/safe-stream.tsx
import { ErrorBoundary } from 'react-error-boundary'
import { Suspense } from 'react'
export function SafeStream({ children, fallback, errorFallback }) {
return (
<ErrorBoundary fallback={errorFallback}>
<Suspense fallback={fallback}>{children}</Suspense>
</ErrorBoundary>
)
}
Ich habe damit drei echte Bugs gefunden – ein Drittanbieter-Analytics-Call, der still scheiterte, ein A/B-Test-Query, der bei einem Cookie-Mismatch fehlwarf, und ein CMS-Endpoint, der während eines Deploys 502 zurückgab. Alle drei wurden vorher als „die Seite sieht komisch aus" ausgeliefert. Mit der Boundary wurde jeder zum graceful Fallback.
Gestreamtes HTML cachen, ohne Personalisierung zu zerstören
Edge-Cache-Schichten (Cloudflare Cache, Vercel Edge Cache, Fastly) wollen das vollständige HTML cachen. Ihr gestreamtes HTML ist nun ein bewegliches Ziel, weil Chunks über Zeit ankommen. Zwei Pattern, die funktionieren:
- Die Shell anpinnen, den Rest streamen. Statisches HTML bis zu einem bekannten Marker (z.B.
<!--ssr-end-of-shell-->) ist vollständig cachebar. Ab dem Marker ist der dynamische Stream. Cloudflares Cache API erlaubt es, nur die Shell percache.matchaufRange:-Header-Semantik in Ihrem Worker zu cachen. Cache-Control: public, max-age=0, s-maxage=300auf der Shell undprivateauf Chunks. Ich mache das mit einemResponse-Interceptor im Worker, der denReadableStreaman einer Puffergrenze teilt, die ersten N Bytes cached und den Rest ungecacht streamt.
Cloudflares Cache API Docs beschreiben die Regeln und Limits. Der Split ist ca. 12 Zeilen Code in einer cache-Middleware.
Streaming hilft nicht, wenn Ihre DB langsam ist
Ehrliche Grenze: Wenn Ihr Edge Worker die Streaming-Boundary schnell erreicht, aber Ihre Origin-DB 3 Sekunden braucht, um zu antworten, haben Sie sich 35ms Cold-TTFB gekauft und 2,95 Sekunden blankes <RelatedDocs />. Der Nutzer verlässt trotzdem die Seite. Streaming verstärkt den Unterschied zwischen schnellen und langsamen Fetches; es behebt langsame Fetches nicht. Verschieben Sie den langsamen Read in einen Loader-Time-Cache oder akzeptieren Sie die Latenz.
Echte Benchmarks: Edge Streaming vs. Traditionelles SSR
Ich habe dieselbe TanStack Start Route (Docs-Seite mit zwei Suspense-Boundaries) über WebPageTest aus sechs Regionen im Mai 2026 getestet. Drei Durchläufe pro Region. Zahlen sind p50, sofern nicht anders angegeben.
Lighthouse Performance Score (Sechs regionale Mediane)
| Stack | Performance | LCP | TBT | CLS |
|---|---|---|---|---|
TanStack Start on Node + renderToString | 71 | 3,20s | 280ms | 0,04 |
| TanStack Start on Node + Streaming | 84 | 2,10s | 160ms | 0,04 |
| TanStack Start on Workers + Streaming | 96 | 1,10s | 40ms | 0,02 |
Echte Nutzerzahlen (RUM) aus meinen zwei Apps
Zahlen aus der web-vitals-Bibliothek, die ich in TanStack Ship's Boilerplate ausliefere, erfasst über 4 Wochen im Juni/Juli 2026:
- LCP p75: 3,2s bei Node-Streaming → 1,1s bei Workers-Streaming
- INP p75: 220ms → 130ms (innerhalb der "Good INP"-Empfehlungen)
- Absprungrate bei First Contentful Paint < 1,0s: 28% → 41% (12% mehr Nutzer sahen Inhalte innerhalb von 1s auf Workers)
- SEO-Impressionen von Cloudflare-Popular-Region-Seiten: +18% über sechs Wochen (Google Re-Ranking)
Hinweis: Ich habe Design-Änderungen kontrolliert, aber der Workers-Rollout überlappte mit einem 14%igen Traffic-Anstieg durch ein Google Core Update. Ihre Zahlen werden abweichen.
Was TanStack Ship standardmäßig ausliefert
TanStack Ship ist standardmäßig für Edge-Streaming konfiguriert. Das Cloudflare-Preset ist aktiviert, das Streaming-Flag ist aktiviert, und drei Boundaries sind bereits in der Marketing-Route-Vorlage vorhanden – Header, Hero und Pre-Footer-CTA. Sie können die Presets umschalten, wenn Sie Node behalten möchten, aber Sie werden ca. 200ms Cold-TTFB pro Route aufgeben. Die meisten Teams behalten die Standardeinstellung.
Als ich TanStack Ship's Preisseite nach diesem Pattern neu gebaut habe, stieg der Lighthouse-Score in einem einzigen PR von 78 auf 94. Die Features-Seiten erreichten 97. Routen, von denen ich angenommen hatte, sie seien „schnell genug", waren tatsächlich 60% schneller als die frühere SaaS-Vorlage des Teams.
Ich habe denselben Ansatz auch bei einer Founder-Marktplatz-App angewendet, für die ich berate. Lighthouse stieg von 64 auf 91 in fünf Tagen. Die Änderung war ein Suspense-Boundary-Refactoring – es gab kein Rewrite. Das ist der ganze Punkt von Streaming SSR on Edge: kleine Codeänderung, sichtbare Lighthouse-Delta, organischer Traffic-Rückenwind.
FAQ: Streaming SSR On Edge
Beeinträchtigt Streaming SSR SEO?
Nein. Googlebot rendert gestreamtes HTML inkrementell und wartet auf das vollständige Dokument, bevor es indexiert. Wenn Sie eine vernünftige Shell mit Titel, Meta-Tags und primärem Copy verschiffen, indexiert Google diesen Inhalt wie jede andere gerenderte Seite. Die Sorge um „no-index wegen Streaming" ist ein Ammenmärchen. Google's Rendering-Docs bestätigen, dass gestreamtes HTML funktioniert. Meine Seiten verloren null Rankings während der Umstellung.
Kann ich von einem anderen Edge-Provider streamen?
Ja, mit Einschränkungen. Die Runtime muss eine WHATWG Streams-API-Implementierung bieten, auf die der React-Renderer abzielen kann. Cloudflare Workers, Vercel Edge (V8-Isolate), Deno Deploy und Netlify Edge erfüllen alle diese Anforderung. AWS Lambda@Edge verwendet eine andere Runtime und Sie müssen React's Streaming-Ausgabe in einen anderen Stream-Typ kompilieren – möglich, aber nicht ergonomisch. Workers bleibt der glatteste Weg.
Was kostet das?
Edge-Streaming hat einen CPU-Kostenanteil pro Request, der davon abhängt, wie stark die gestreamte Boundary von Daten abhängt. Für meine Docs-Seite – die eine 6-zeilige Empfehlungsliste streamt – berechnet Workers gemäß der Cloudflare-Preisseite ca. 0,00002 $ pro Request. Bei 10M Streaming-Requests/Monat sind das ca. 200 $ an Compute – deutlich günstiger als der equivalente Node-Cluster.
Verwandte Beiträge
- Cloudflare D1 vs Supabase Database Choice — Dieselbe Edge-First-Perspektive, auf Persistenz angewandt
- Lighthouse 95+ Optimization Case Study — Was zu tun ist, nachdem Streaming eingerichtet ist
- Why TanStack Ship Wins for Edge Performance — Vollständiger Boilerplate-Vergleich
- TanStack Start routes pricing and templates — Was enthalten ist
Wenn Sie heute ein SaaS starten, setzen Sie die SSR-Schicht standardmäßig auf Streaming auf Workers und schreiben Sie die Suspense-Boundaries zuerst. TanStack Ship hat dies standardmäßig aktiviert; andernfalls führen Sie die TanStack Start Docs durch die Aktivierung. In jedem Fall: Bringen Sie das Shell-Streaming zum Laufen – LCP und Absprungrate werden es Ihnen danken.
— Huifer