Cloudflare Workers: Der komplette Produktions-Implementierungsleitfaden (2026)

Alles, was ich beim Deployment von 12+ Produktions-SaaS-Apps auf Cloudflare Workers gelernt habe: Isolate-Modell, D1, R2, KV, kalte Starts und Limits.

Huifer
Huifer
29. Juni 202613 min read
Auch verfügbar auf:English · 中文

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe seit 2023 12+ Produktions-SaaS-Anwendungen auf Cloudflare Workers veröffentlicht — Auth, Abrechnung, transaktionale E-Mails, Hintergrundjobs, WebSockets, all das in der Edge-Runtime. Das bedeutet, dass ich die Plattform auch auf die interessantesten Arten gebrochen habe: D1-Write-Lock-Konflikte bei gleichzeitigem Checkout, die Obergrenze von 1.000 Subrequests bei einem Fan-Out-Job, eine heiße Partition von Durable Objects, die einen gesamten Mandanten serialisierte. Jedes Limit und jeder Preis weiter unten ist mit der offiziellen Cloudflare-Dokumentation verlinkt und nicht aus dem Gedächtnis behauptet, und jeder Fehlermodus ist einer, den ich persönlich in der Produktion debuggt habe. TanStack Ship ist mein kostenpflichtiges Produkt; ich nenne diese Voreingenommenheit im Voraus.

Verifizierte Quellen: Cloudflare Workers docs · Workers limits · Workers pricing · D1 · Durable Objects · Queues · R2 · workerd runtime

Zuletzt aktualisiert: 29. Juni 2026 · Changelog


TL;DR: Cloudflare Workers führt Ihr JavaScript in einem V8-Isolate anstelle eines Containers aus, weshalb Kaltstarts unter 50 ms statt 800 ms liegen und weshalb Sie für CPU-Millisekunden anstelle von Echtzeit (Wall-Clock Time) bezahlen. Die Plattform ist produktionsbereit für die volle SaaS-Struktur — D1 für relationale Daten, R2 für Objekte mit null Egress-Gebühren (ausgehender Traffic), KV für leselastige Konfigurationen, Queues für asynchrone Arbeit, Durable Objects für zustandsbehaftete Koordination. Die wahren Einschränkungen liegen nicht in der Leistung; es sind die CPU-Obergrenze pro Anfrage, die Subrequest-Obergrenze, das Fehlen von rohem TCP für veraltete Datenbanktreiber und das Single-Writer-Modell von D1. Dieser Leitfaden behandelt das Ausführungsmodell, alle sechs Binding-Familien, vier Fehlermodi, die ich in der Produktion persönlich erlebt habe, die Kostenberechnung im Vergleich zu einer Container-Baseline sowie einen Entscheidungsrahmen, um zu wissen, wann Workers die falsche Wahl ist.


Das Ausführungsmodell: Warum Isolates Ihre Architektur verändern

Die meisten Serverless-Plattformen geben Ihnen einen Container. Cloudflare Workers gibt Ihnen ein V8-Isolate — die gleiche primitive Funktion, die Browser-Tabs voneinander trennt. Diese einzige Designentscheidung erklärt fast jede sich daraus ergebende Eigenschaft der Plattform, sowohl im Guten als auch im Schlechten.

Isolates versus Container, konkret

Eine containerbasierte Funktion (Lambda, Cloud Run im Request-Modus) muss einen Prozess starten, eine Sprachlaufzeitumgebung laden und Ihren Abhängigkeitsbaum (Dependency Tree) initialisieren, bevor Ihr Handler ausgeführt wird. Das ist der Kaltstart (Cold Start), und bei einer Node.js-Funktion mit einem moderaten Abhängigkeitsgraphen liegt er irgendwo zwischen 300 ms und 1,2 s. Workers startet stattdessen ein Isolate innerhalb eines bereits laufenden workerd-Prozesses. Ihr Skript wird einmal kompiliert und als Snapshot gespeichert; das Hochfahren eines Ausführungskontexts wird in einstelligen Millisekunden gemessen.

In der Praxis liegt der p50-Kaltstart bei den von mir überwachten TanStack Ship-Deployments unter 50 ms und der p99 unter 200 ms, einschließlich des TLS-Handshakes am nächstgelegenen der ~330 Cloudflare-Standorte. Ich habe keinen Benchmark für eine anhaltende Last von über etwa 400 req/s pro App durchgeführt, betrachten Sie Durchsatzbehauptungen darüber hinaus also als von mir ungetestet.

Was Isolates Ihnen wegnehmen

Das Isolate-Modell ist nicht kostenlos. Sie erhalten kein Dateisystem, keine rohen TCP-Sockets im klassischen POSIX-Sinne und keinen langlebigen Prozess, bei dem Sie einen Connection-Pool für Stunden cachen können. Native Node-Addons fallen weg. Alles, was auf ein Binary ausweicht, fällt weg. Wenn Ihre Architektur davon abhängt, dass pg einen dauerhaften Socket zu Postgres öffnet, müssen Sie entweder zu Hyperdrive wechseln, einem Treiber mit einem HTTP-Transport, oder zu D1.

Das CPU-Zeit-Abrechnungsmodell

Workers rechnet in CPU-Millisekunden ab, nicht in Echtzeit (Wall-Clock). Ein Endpunkt, der auf eine 900 ms dauernde API von Drittanbietern wartet und dann 3 ms JSON-Arbeit verrichtet, kostet Sie 3 ms CPU, nicht 903 ms Dauer. Dies ist der größte Kostenunterschied im Vergleich zu Lambda bei I/O-lastigen SaaS-Endpunkten, und das ist der Grund, warum Webhook-Empfänger und API-Proxys bei Workers geradezu absurd günstig sind. Die offizielle Pricing-Seite ist bindend; überprüfen Sie die aktuellen Zahlen dort, bevor Sie darauf basierend ein Budget erstellen.

Bindings: Die sechs Primitive, die Workers zu einer Plattform machen

Ein rudimentärer Worker ist ein Fetch-Handler. Was ihn zu einer Anwendungsplattform macht, sind Bindings — Funktionen, die zur Anfragezeit in die env injiziert werden, ohne Connection-String, ohne Credential-Rotation und ohne kalte Verbindungskosten.

Bindings in der wrangler.jsonc deklarieren

Jedes Binding beginnt als Konfiguration. Dies ist eine gekürzte Version der echten Konfigurationsstruktur, die ich verwende:

jsonc
// wrangler.jsonc
{
  "name": "ship-app",
  "main": "src/worker.ts",
  "compatibility_date": "2026-06-01",
  "compatibility_flags": ["nodejs_compat"],
  "observability": { "enabled": true },

  "d1_databases": [
    { "binding": "DB", "database_name": "ship-prod", "database_id": "<uuid>" }
  ],
  "r2_buckets": [
    { "binding": "UPLOADS", "bucket_name": "ship-uploads" }
  ],
  "kv_namespaces": [
    { "binding": "CONFIG", "id": "<namespace-id>" }
  ],
  "queues": {
    "producers": [{ "binding": "EMAIL_QUEUE", "queue": "ship-email" }],
    "consumers": [
      { "queue": "ship-email", "max_batch_size": 10, "max_retries": 3,
        "dead_letter_queue": "ship-email-dlq" }
    ]
  },
  "durable_objects": {
    "bindings": [{ "name": "ROOM", "class_name": "CollabRoom" }]
  },
  "ai": { "binding": "AI" }
}

Zwei Details sind wichtiger, als sie aussehen. compatibility_date verankert die Laufzeitsemantik — eine Änderung kann das Verhalten verändern, betrachten Sie dies also als bewusste Migration, nicht als rein kosmetisches Update. Und observability.enabled schaltet die Logs ein, die Sie bei Ihrem ersten Vorfall verzweifelt benötigen werden; schalten Sie sie am ersten Tag ein, nicht erst danach.

D1 für relationale Daten — und seine einzige harte Einschränkung

D1 ist SQLite an der Edge mit einem an HTTP angelehnten Transport und es ist der richtige Standard für Mandantentabellen, Abonnements und Audit-Logs. Verwenden Sie Prepared Statements mit .bind() — und zwar immer, sowohl zur Vermeidung von Injections als auch, weil D1 den Plan für die Ausführung zwischenspeichert. Verwenden Sie batch() für Atomarität bei mehreren Anweisungen.

Die Einschränkung, die Sie treffen wird: D1 erlaubt nur einen einzigen Writer pro Datenbank. Lesevorgänge skalieren über Replicas; Schreibvorgänge werden serialisiert. Ich habe den Vorfall, den dies verursachte, im Produktionsleitfaden für D1 behandelt, aber die Kurzfassung lautet, dass ein schreibintensiver Hot-Path in Kombination mit einer langen Transaktion zu Write-Lock-Konflikten führt. Die Lösung besteht in kürzeren Transaktionen und der Auslagerung stark schwankender Zähler aus D1.

R2, KV und die Lese-/Schreib-Asymmetrie

R2 ist ein S3-kompatibler Objektspeicher mit null Egress-Gebühren, was für ein Produkt, das von Benutzern hochgeladene Medien bereitstellt, den Unterschied zwischen einem tragfähigen und einem nicht tragfähigen wirtschaftlichen Modell ausmacht. Verwenden Sie es für Uploads, Exporte, Backups und generierte Assets.

KV ist eventuell konsistent (eventually consistent), global repliziert und für leselastige Daten optimiert — Feature-Flags, Plan-Konfigurationen, zwischengespeicherte gerenderte Fragmente. Seine Schreibverbreitung erfolgt nicht unmittelbar, sondern legt sich typischerweise innerhalb einer Minute. Speichern Sie niemals einen Wert im KV, der innerhalb desselben Requests korrekt abgerufen werden muss, der ihn geschrieben hat. Ich habe genau ein Team gesehen, das versucht hat, den Session-Zustand im KV zu speichern, und dann eine Woche damit verbracht hat, Phantom-Logouts zu debuggen.

Queues, Durable Objects und Workers AI

Queues entkoppeln langwierige Arbeit vom Request-Pfad: Sie reihen die Aufgabe im Handler ein und konsumieren sie in einem separaten Worker mit Batching, Retries und einer Dead-Letter-Queue. Jede transaktionale E-Mail in meinen Apps durchläuft eine Queue, da ein SMTP-Anbieter, der einen schlechten Nachmittag hat, sich niemals in einer fehlgeschlagenen Registrierung auswirken darf.

Durable Objects bieten Ihnen einen Single-Threaded, global adressierbaren zustandsbehafteten Aktor mit seinem eigenen transaktionalen Speicher und nativem WebSocket-Hibernation. Sie sind das richtige Primitiv für kollaborative Räume, Rate Limiter und eine pro Mandant (per-tenant) basierende Sequenzierung. Workers AI (env.AI) führt Inference auf Cloudflare GPUs an der Edge durch, ohne jegliches Key-Management, was wirklich der schnellste Weg von der Idee zur ausgelieferten LLM-Funktion ist, den ich je genutzt habe.

Ein Handler, der drei Bindings gleichzeitig verwendet

typescript
// src/worker.ts
export interface Env {
  DB: D1Database;
  EMAIL_QUEUE: Queue<{ to: string; template: string }>;
  CONFIG: KVNamespace;
}

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });

    const { email } = await req.json<{ email: string }>();
    const plan = (await env.CONFIG.get("default_plan")) ?? "free";

    // Single-Writer D1: Halten Sie das Zeitfenster für Schreibvorgänge so klein wie möglich.
    const { meta } = await env.DB
      .prepare("INSERT INTO users (email, plan) VALUES (?1, ?2)")
      .bind(email, plan)
      .run();

    // Warten Sie niemals auf langsame Seiteneffekte (Side Effects) im Request-Pfad.
    ctx.waitUntil(
      env.EMAIL_QUEUE.send({ to: email, template: "welcome" })
    );

    return Response.json({ id: meta.last_row_id, plan }, { status: 201 });
  },
} satisfies ExportedHandler<Env>;

ctx.waitUntil() ist die am meisten unterschätzte API auf der Plattform. Sie lässt die Response sofort zurückkehren, während die Runtime den Seiteneffekt (Side Effect) abschließt. Analytics-Schreibvorgänge, Cache-Aufwärmvorgänge (Cache Warms) und Queue-Sends gehören genau dorthin.

Vier Fehlermodi in der Produktion, die ich tatsächlich erlebt habe

Dokumentationen nennen Ihnen die Limits. Die Produktion zeigt Ihnen, auf welche Limits Sie tatsächlich stoßen werden. Diese vier haben mich reale Arbeitsstunden gekostet.

Die Obergrenze von 1.000 Subrequests bei einem Fan-Out

Ein nächtlicher Job iterierte über Mandanten und rief einmal pro Mandant eine externe API zur Datenanreicherung (Enrichment) auf. Bei 1.100 Mandanten begannen Fehler aufzutreten. Jeder fetch() von einem Worker aus ist ein Subrequest, und der kostenpflichtige Tarif limitiert diese pro Aufruf — siehe Workers limits für die aktuellen Zahlen. Die Lösung war kein höheres Limit; es war die richtige Architektur: Eine Nachricht pro Mandant in eine Queue einreihen und den Konsumenten (Consumer) Batches von jeweils zehn verarbeiten lassen. Jeder Konsumenten-Aufruf nutzt nun zehn Subrequests statt 1.100.

Erschöpfung der CPU-Zeit bei einer synchronen Schleife

Ein Export-Endpunkt baute eine CSV-Datei im Arbeitsspeicher auf, indem er Strings über ~80.000 Zeilen verkettete. Die Echtzeit (Wall-Clock) war in Ordnung; die CPU-Zeit sprengte jedoch das Limit pro Aufruf, und der Request wurde mittendrin abgebrochen. Zwei Änderungen behoben das Problem: Das Streaming der Antwort mit einem TransformStream, sodass Bytes weitergeleitet werden, sobald sie erzeugt sind, und die Verlagerung der Generierung auf einen Queue-Konsumenten, der die fertige Datei im R2 speichert und einen signierten Link per E-Mail sendet. Streaming ist generell die Antwort auf fast jedes Problem mit CPU-Limits auf Workers.

Eine heiße Partition von Durable Objects

Ich habe ein Rate Limiter Durable Object nach API-Route anstelle des Mandanten (Tenant) geschlüsselt (keyed). Eine beliebte Route bedeutete, dass ein einzelnes Objekt jede Anfrage für jeden Kunden abwickeln musste, und da DOs Single-Threaded sind, wurde dieses Objekt zu einem globalen Serialisierungspunkt. Die Latenz beim p99 stieg von 40 ms auf 900 ms. Die Umverteilung nach tenantId behob den Fehler durch eine einzeilige Änderung an der ID-Ableitung. Die Performance von Durable Objects hängt vollständig von Ihrem Schlüssel-Design (Key Design) ab. Ich habe eine regionsübergreifende DO-Migration unter Last nicht getestet, weshalb ich dazu auch keine Behauptungen aufstelle.

Kaltstart bei einem Checkout mit erster Anfrage

Das einzige echte Kaltstartproblem (Cold Start), das ich erlebte, lag nicht an der Runtime — es war ein 340 KB großer Abhängigkeitsgraph (Dependency Graph), der beim allerersten Aufruf verarbeitet wurde, sowie ein im Modul-Scope erstellter Stripe-Client, der sofort Arbeit durchführte. Das Beschneiden des Bundles (Bundle Size) und das verzögerte Laden von Clients (Lazy Loading) innerhalb des Handlers brachten die Zeit bis zum ersten Byte wieder unter Kontrolle. Die Lektion: Auf Workers ist die Größe Ihres Bundles Ihr Kaltstart. Ich habe dies ausführlicher in der Kaltstart-Postmortem für Workers beschrieben.

Die Kostenberechnung im Vergleich zu einer Container-Baseline

Hier ist der Punkt, an dem Workers kommerziell gewinnt, und es lohnt sich, die Arithmetik durchzuführen, anstatt sich auf ein Gefühl zu verlassen.

Die Zusammensetzung der Rechnung

Sie zahlen im Paid Plan eine kleine monatliche Plattformgebühr und danach pro Million Requests sowie pro Million CPU-Millisekunden. Bindings werden separat abgerechnet: D1 nach gelesenen/geschriebenen Zeilen, R2 nach Speicher und Operationen (nicht jedoch Egress), KV nach Reads/Writes, Queues nach Operationen. Überprüfen Sie die Pricing-Seite auf aktuelle Preise — diese ändern sich, und ich werde keinen fest codierten Wert nennen, der bald wieder veraltet ist.

Warum das I/O-Profil so entscheidend ist

Nehmen Sie einen typischen SaaS-API-Endpunkt: 40 ms Warten auf D1 und Stripe, 4 ms tatsächliche Rechenleistung (Compute). Auf einer nach Dauer (Duration) abgerechneten Plattform zahlen Sie für 44 ms. Auf Workers zahlen Sie für 4 ms. Bei einer App mit 10 Millionen Anfragen pro Monat, die primär I/O-Arbeiten durchführt, macht dieses Verhältnis bei den Rechenkosten ungefähr einen Unterschied in der Größenordnung eines ganzen Faktors aus. Ohne zusätzliche Egress-Kosten von R2, ohne NAT-Gateway, ohne Load Balancer und ohne eine Mindestanzahl an ungenutzten Instanzen (Idle Instance Floor) vergrößert sich die Gesamtkostenschere weiter.

Wo Workers die falsche Wahl ist

Ich werde nicht so tun, als ob es universell einsetzbar wäre. Entscheiden Sie sich nicht für Workers, wenn Sie langanhaltende Berechnungen pro Request benötigen (Video-Transkodierung, große Modell-Trainings), wenn Sie einen klassischen Datenbanktreiber über rohes TCP benötigen, vor den Hyperdrive nicht geschaltet werden kann, wenn die operationelle Erfahrung (Muscle Memory) Ihres Teams vollständig auf Kubernetes ausgerichtet ist und die Migrationskosten die Einsparungen übersteigen, oder wenn Sie auf einer einzelnen relationalen Datenbank mehr Schreibdurchsatz benötigen, als das Single-Writer-Modell von D1 liefern kann. Im letzten Fall ist Postgres hinter Hyperdrive die ehrliche Empfehlung, was ich auch im Edge-Stack-Vergleich so anspreche.

Ein Entscheidungs- und Implementierungsrahmen

Bewerten Sie Ihr Projekt anhand dieser fünf Fragen, bevor Sie sich festlegen.

Der Fünf-Fragen-Filter

  1. Liegt der Rechenaufwand pro Request unter ein paar hundert CPU-Millisekunden? Wenn ja, passt Workers.
  2. Kann jeder Datenspeicher (Datastore) über HTTP oder ein Workers-Binding erreicht werden? Wenn nein, kalkulieren Sie zunächst Hyperdrive.
  3. Ist Ihr Schreibvolumen auf einer einzelnen relationalen Datenbank überschaubar? Wenn nein, planen Sie mit Postgres, nicht mit D1.
  4. Haben Sie signifikante Egress-Volumina? Wenn ja, kann allein R2 die Plattform rechtfertigen.
  5. Brauchen Sie zustandsbehaftete (stateful) Koordination? Wenn ja, sind Durable Objects ein echtes Unterscheidungsmerkmal, für das es andernorts kein sauberes Äquivalent gibt.

Vier oder fünf Ja-Antworten bedeuten: Bauen Sie auf Workers. Zwei oder weniger bedeuten, dass Sie gegen die Plattform kämpfen müssen.

Die Implementierungsreihenfolge, die für mich funktioniert hat

Beginnen Sie mit dem Fetch-Handler und dem D1-Schema. Fügen Sie anschließend Auth hinzu, denn jede spätere Entscheidung hängt von der Session-Struktur ab. Danach Billing, da Berechtigungsprüfungen jede Route berühren. Dann lagern Sie alle langsamen Seiteneffekte (Side Effects) in einem Rutsch auf Queues aus, anstatt dies Endpunkt für Endpunkt nachzurüsten. Fügen Sie schließlich Observability und einen Dead-Letter-Queue-Alarm hinzu, bevor Sie echten Traffic annehmen. Observability als Letztes zu erledigen, ist ein Fehler, den ich nur einmal gemacht habe.

Wie das vormontiert aussieht

Die obige Sequenz besteht aus ungefähr 2.400 Zeilen Glue-Code — Auth, Stripe-Webhooks mit Idempotenz, D1-Migrationen, Queue-Konsumenten, signierte R2-Uploads, DO Rate Limiting. Es dauerte beim ersten Mal etwa 18 Stunden und danach bei jeder der zwölf Apps sukzessive weniger Zeit; genau deshalb habe ich es als Template extrahiert. Wenn Sie diese nicht ein dreizehntes Mal schreiben möchten, ist TanStack Ship genau dafür da.

Fazit

Cloudflare Workers ist im Jahr 2026 kein Experiment mehr. Das Isolate-Modell ist ein echter architektonischer Vorteil bei I/O-gebundener SaaS-Arbeit, das Ökosystem aus Bindings deckt die Anforderungen einer kompletten Anwendung ab, und die Kostenstruktur begünstigt Sie in jeder Größenordnung, in der ich bisher operiert habe. Die wahren Grenzen der Plattform — CPU-Deckelung, Subrequest-Maximum, Single-Writer bei D1, fehlendes natives TCP — sind allesamt im Vorfeld bekannt, und für jede von ihnen existiert eine dokumentierte architektonische Lösung.

Dies ist bewusst ein einziger umfassender Artikel und nicht in sechs Fragmente aufgeteilt. Das Ausführungsmodell, jedes Binding, die Fehlerquellen, die Kostenberechnung und der Entscheidungsrahmen sind alle hier zu finden. Wenn Sie tiefer in eine bestimmte Schicht eindringen möchten, vertiefen dies der D1-Produktionsleitfaden sowie der Leitfaden zur Leistungsoptimierung.

Wenn Sie sich für Workers als Plattform entschieden haben und lieber auf einer Produktionsbasis statt mit einem leeren Fetch-Handler starten wollen, sehen Sie sich die 14 Module von TanStack Ship an oder werfen Sie einen Blick auf die Preise. Einmalige Lizenz, lebenslange Updates, 14-tägiges Rückgaberecht, falls die Module nicht zu Ihrem Stack passen.

Schreiben Sie die 2.400 Zeilen selbst, wenn Sie das Wochenende Zeit haben. Kaufen Sie sich die Zeit zurück, wenn Sie diese lieber in den Teil Ihres Produkts investieren möchten, den niemand sonst entwickeln kann.


Verwandte Literatur: