TanStack Start Deployment: In 3 Minuten auf den Edge deployen (Echtes Setup)

Echte Deployment-Pipeline. TanStack Start auf Cloudflare Workers in 3 Minuten deployen. CI/CD-Setup, Preview-Deployments und Rollback-Strategie.

Sam Rivera
Sam Rivera
29. Mai 202611 min read
Auch verfügbar auf:English · 中文

Kurzfassung: Das Deployen einer TanStack Start-Anwendung auf Cloudflare Workers erfordert eine sorgfältige Konfiguration von wrangler.jsonc, ein durchdachtes Management von Umgebungsvariablen zwischen Dev- und Produktionsumgebungen, die Einrichtung einer D1-Datenbank mit automatisierten Migrations, die Integration von R2 Object Storage sowie eine solide CI/CD-Pipeline. Dieser Guide begleitet Sie durch jeden Schritt des Prozesses – vom initialen Scaffolding bis zum Production-Monitoring –, damit Sie mit voller Zuversicht auf Cloudflares Edge-Netzwerk deployen können.


Einleitung: Warum Cloudflare Workers für TanStack Start

TanStack Start ist ein Full-Stack-React-Framework, das auf Vinxi aufbaut und Serverseitiges Rendern, Streaming sowie Static Generation in das TanStack-Ökosystem bringt. Was das Deployment betrifft, bieten Cloudflare Workers eine einzigartig überzeugende Laufzeitumgebung für Start-Anwendungen.

Anders als traditionelle Node.js-Server, die Cold Starts, Regions-Pinning und manuelles Skalieren erfordern, laufen Cloudflare Workers auf V8-Isolates über 330+ Standorte weltweit. Das bedeutet: Ihre TanStack Start-App kann initiales HTML ausliefern, Content-Updates streamen und API-Anfragen bearbeiten – alles vom Edge aus, nah an jedem Nutzer.

Doch mit diesem architektonischen Wandel gehen auch neue Konfigurationsherausforderungen einher. Die Workers-Laufzeitumgebung unterstützt keine Node.js-APIs wie fs, net oder child_process. Ihre Build-Pipeline, Umgebungsvariablen und Datenbankverbindungen müssen alle angepasst werden. Dieser Guide deckt das produktionsreife Setup ab, mit dem wir TanStack Start auf Cloudflare Workers im großen Maßstab betreiben.


wrangler.jsonc: Tiefgehende Konfiguration

Die wrangler.jsonc-Datei ist die zentrale Anlaufstelle für Ihre Cloudflare-Deployment-Konfiguration. Hier ist die Produktionskonfiguration, die wir verwenden – mit Erklärungen versehen:

jsonc
{
  "$schema": "https://raw.githubusercontent.com/cloudflare/workers-sdk/main/packages/wrangler/config-schema.json",
  "name": "tanstack-ship",
  "main": "./dist/worker.js",
  "compatibility_date": "2026-05-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist/public",
    "binding": "ASSETS",
    "html_handling": "auto",
    "not_found_handling": "404"
  },
  "workers_dev": false,
  "route": {
    "pattern": "tanstack-ship.com",
    "custom_domain": true,
    "zone_id": "YOUR_ZONE_ID"
  },
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "tanstack-ship-db",
      "database_id": "YOUR_D1_DATABASE_ID",
      "migrations_dir": "./migrations"
    }
  ],
  "r2_buckets": [
    {
      "binding": "R2",
      "bucket_name": "tanstack-ship-assets"
    }
  ],
  "vars": {
    "APP_VERSION": "1.0.0",
    "SENTRY_DSN": ""
  },
  "env": {
    "production": {
      "vars": {
        "SENTRY_DSN": "https://your-sentry-dsn@o123.ingest.sentry.io/456"
      },
      "routes": [
        {
          "pattern": "tanstack-ship.com",
          "custom_domain": true
        }
      ]
    },
    "preview": {
      "workers_dev": true,
      "vars": {
        "SENTRY_DSN": ""
      }
    }
  },
  "observability": {
    "enabled": true,
    "head_sampling_rate": 0.1
  }
}

Erklärung der wichtigsten Felder

  • compatibility_flags: Das Flag nodejs_compat aktiviert eine Teilmenge der Node.js-APIs (z. B. Buffer, process.env). Viele npm-Pakete sind darauf angewiesen – halten Sie dieses Flag aktiviert.
  • assets: html_handling: "auto" weist Workers an, index.html für Verzeichnisanfragen auszuliefern – essenziell für den Client-seitigen Routing-Fallback. not_found_handling: "404" stellt sicher, dass unbekannte Pfade einen korrekten 404 zurückgeben.
  • env-Blöcke: Definieren Sie umgebungsspezifische Overrides. Die preview-Umgebung nutzt workers_dev: true für *.workers.dev-Subdomains, während production eine Custom Domain bindet.

Umgebungsvariablen: Dev vs. Produktion

Lokale Entwicklung

Erstellen Sie eine .dev.vars-Datei im Projekt-Root:

# .dev.vars – wird von wrangler dev geladen
SENTRY_DSN=
STRIPE_SECRET_KEY=your-stripe-secret-key
RESEND_API_KEY=your-resend-api-key
PUBLIC_SITE_URL=http://localhost:5173

Der wrangler dev-Befehl von Cloudflare liest .dev.vars automatisch ein und injiziert die Werte in die Worker-Laufzeit.

Produktions-Secrets

Für Produktions-Secrets nutzen Sie wrangler secret put, um sensible Werte aus Ihren Konfigurationsdateien herauszuhalten:

bash
npx wrangler secret put SENTRY_DSN
npx wrangler secret put STRIPE_SECRET_KEY

Secrets werden verschlüsselt gespeichert und zur Laufzeit auf dem Edge injiziert. Sie sind nicht im vars-Abschnitt von wrangler.jsonc zugänglich – ein häufiger Sicherheitsfehler.

Typsicheres Zugriffsmuster

Zentralisieren Sie den gesamten Zugriff auf Umgebungsvariablen in einem einzigen Modul mit Zod-Validierung für Laufzeitsicherheit:

typescript
// src/env.ts
import { z } from "zod";

const envSchema = z.object({
  SENTRY_DSN: z.string().optional(),
  STRIPE_SECRET_KEY: z.string().min(1),
  RESEND_API_KEY: z.string().min(1),
  PUBLIC_SITE_URL: z.string().url().default("http://localhost:5173"),
  DB: z.any(),    // D1-Bindung
  R2: z.any(),    // R2-Bindung
  ASSETS: z.any(), // Assets-Bindung
});

export type Env = z.infer<typeof envSchema>;

export function parseEnv(env: Record<string, unknown>): Env {
  const result = envSchema.safeParse(env);
  if (!result.success) {
    console.error("Ungültige Umgebungsvariablen:", result.error.flatten());
    throw new Error("Umgebungsvalidierung fehlgeschlagen");
  }
  return result.data;
}

Dieses Muster bietet Ihnen Laufzeit-Typsicherheit und erkennt fehlkonfigurierte Umgebungen früh in der CI.


D1-Datenbank: Einrichtung und Migrations

1. Datenbank erstellen

bash
npx wrangler d1 create tanstack-ship-db

Kopieren Sie die zurückgegebene database_id in Ihre wrangler.jsonc.

2. Migrations schreiben

Platzieren Sie SQL-Migrationsdateien in einem migrations/-Verzeichnis:

sql
-- migrations/0001_users.sql
CREATE TABLE IF NOT EXISTS users (
  id TEXT PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_users_email ON users(email);

3. Migrations anwenden

Lokal:

bash
npx wrangler d1 migrations apply tanstack-ship-db --local

In Produktion:

bash
npx wrangler d1 migrations apply tanstack-ship-db --remote

4. Automatisierte Migrations in der CI

yaml
# .github/workflows/deploy.yml (Auszug)
- name: D1-Migrations anwenden
  run: npx wrangler d1 migrations apply tanstack-ship-db --remote
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Führen Sie Migrations vor dem Deployment der neuen Worker-Version durch, um Zero-Downtime-Deployments zu gewährleisten.


R2 Object Storage: Integration

Fügen Sie eine R2-Bindung in wrangler.jsonc hinzu und konfigurieren Sie dann den Client für Datei-Uploads und -Abruf:

typescript
// src/lib/r2-client.ts
import type { R2Bucket } from "@cloudflare/workers-types";

let bucket: R2Bucket;

export function initR2(r2: R2Bucket) {
  bucket = r2;
}

export async function uploadFile(
  key: string,
  body: ReadableStream | ArrayBuffer,
  contentType: string
) {
  return bucket.put(key, body, {
    httpMetadata: { contentType },
    customMetadata: { uploadedAt: new Date().toISOString() },
  });
}

export async function getFile(key: string) {
  const object = await bucket.get(key);
  if (!object) return null;
  return object;
}

Produktions-Optimierungen

Caching-Header

typescript
function setCachingHeaders(response: Response, pathname: string): Response {
  const headers = new Headers(response.headers);

  // Statische Assets: Langzeit-Cache
  if (/\.(js|css|png|jpg|webp|svg|woff2?)$/i.test(pathname)) {
    headers.set("Cache-Control", "public, max-age=31536000, immutable");
  }
  // HTML-Seiten: Kurzer CDN-Cache, kein Browser-Cache
  else if (pathname.endsWith(".html") || !pathname.includes(".")) {
    headers.set("Cache-Control", "public, s-maxage=60, stale-while-revalidate=600");
  }

  return new Response(response.body, {
    status: response.status,
    headers,
  });
}

Streaming aktivieren

jsonc
// wrangler.jsonc
{
  "compatibility_flags": ["nodejs_compat"],
  "streaming": true
}

Streaming ermöglicht es Suspense-Boundaries, Content progressiv auszuliefern – das verbessert die wahrgenommene Performance spürbar.


CI/CD-Pipeline: Einrichtung

yaml
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
      - run: npm ci
      - run: npm run typecheck
      - run: npm run lint
      - run: npm run build
        env:
          PUBLIC_SITE_URL: ${{ vars.PUBLIC_SITE_URL }}
      - name: D1-Migrations anwenden
        run: npx wrangler d1 migrations apply tanstack-ship-db --remote
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
      - name: Auf Cloudflare Workers deployen
        run: npx wrangler deploy --env production
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Häufige Deployment-Probleme und Lösungen

  1. „Module not found" für Node.js Built-ins: Aktivieren Sie das nodejs_compat-Flag. Falls das Modul weiterhin fehlschlägt, finden Sie eine Workers-kompatible Alternative.

  2. D1-Migration schlägt fehl mit „Table already exists": Verwenden Sie stets CREATE TABLE IF NOT EXISTS und CREATE INDEX IF NOT EXISTS.

  3. Client-seitiges Routing gibt 404 bei Refresh zurück: Stellen Sie sicher, dass html_handling: "auto" in wrangler.jsonc gesetzt ist.

  4. Umgebungsvariablen sind zur Laufzeit undefined: Verschieben Sie Secrets zu wrangler secret put und nutzen Sie vars nur für nicht-sensible Konfiguration.

  5. Bundle überschreitet das 1-MB-Limit: Stellen Sie sicher, dass Ihr Vite-Build Ungenutztes entfernt. Nutzen Sie dynamisches import() für schwere Abhängigkeiten.


Fazit

Das Deployen von TanStack Start auf Cloudflare Workers ist eine der performantesten und kosteneffektivsten Arten, eine Full-Stack-React-Anwendung auszuliefern. Die Edge-Laufzeit bietet Ihnen globale Verteilung, instantane Cold Starts und ein großzügiges Free-Tier.

Die wichtigsten Erkenntnisse:

  • Validieren Sie Ihre Umgebungsvariablen mit Zod am Edge, bevor irgendeine Logik ausgeführt wird.
  • Führen Sie Migrations vor Deployments durch, nicht danach.
  • Setzen Sie Caching-Header aggressiv, aber durchdacht – HTML bekommt s-maxage, Assets bekommen immutable.
  • Aktivieren Sie Observability frühzeitig – Sie werden diese Traces brauchen, wenn etwas schiefgeht.

Für eine praxisnahe Anleitung zum Erstellen einer vollständigen Anwendung lesen Sie das TanStack Start End-to-End Tutorial. Um Ihre D1-Datenbank in Produktion zu bringen, lesen Sie Edge Database: Cloudflare D1 in Production. Für offizielle Referenzen konsultieren Sie die Wrangler CLI-, Cloudflare D1- und R2-Dokumentation.