Auf Cloudflare deployen

Auf dieser Seite

Cloudflare Workers bietet eine schnelle, global verteilte Laufzeitumgebung für EmDash. Diese Anleitung behandelt das Deployment mit D1 für die Datenbank und R2 für die Medienspeicherung.

Voraussetzungen

  • Ein Cloudflare-Konto
  • Wrangler CLI installiert (npm install -g wrangler)
  • Bei Cloudflare authentifiziert (wrangler login)

Bindings konfigurieren

Erstellen Sie wrangler.jsonc im Projektstammverzeichnis mit D1- und R2-Bindings. Wrangler stellt beide Ressourcen beim ersten Deploy bereit, wenn sie noch nicht existieren.

{
	"$schema": "node_modules/wrangler/config-schema.json",
	"name": "my-emdash-site",
	"compatibility_date": "2025-01-15",
	"compatibility_flags": ["nodejs_compat"],

	"d1_databases": [
		{
			"binding": "DB",
			"database_name": "emdash-db",
		},
	],

	"r2_buckets": [
		{
			"binding": "MEDIA",
			"bucket_name": "emdash-media",
		},
	],
}

EmDash konfigurieren

Aktualisieren Sie Ihre Astro-Konfiguration für D1 und R2:

import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import react from "@astrojs/react";
import emdash from "emdash/astro";
import { d1, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	output: "server",
	adapter: cloudflare(),
	integrations: [
		react(), // Erforderlich — die Admin-UI ist eine React-App
		emdash({
			database: d1({ binding: "DB" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Erster Start

Datenbankmigrationen werden automatisch bei der ersten Anfrage nach dem Deployment ausgeführt und bei jedem nachfolgenden Start, wenn neue Migrationen vorliegen.

Wenn die Datenbank leer ist (keine Collections) und der Setup-Assistent noch nicht abgeschlossen wurde, wendet EmDash beim ersten Start auch eine Seed-Datei an. Die Seed-Datei wird zur Build-Zeit aus .emdash/seed.json, dem Pfad in package.json#emdash.seed oder seed/seed.json gelesen — was zuerst gefunden wird — und in das Bundle eingebettet. Wenn keine vorhanden ist, wird ein integrierter Standard-Seed verwendet. Nachfolgende Deploys auf eine bestehende Datenbank lassen deren Inhalt unverändert.

Um das Schema oder Inhaltsmodell einer bereits deployed Site zu ändern, siehe Eine deployed Site weiterentwickeln.

Geplante Veröffentlichung

Auf Cloudflare Workers laufen geplante Veröffentlichung, Plugin-Cron und Wartungsaufgaben über einen Worker Cron Trigger. Neue Cloudflare-Templates enthalten dieses Setup automatisch. Wenn Sie ein bestehendes Projekt aktualisieren, exportieren Sie den EmDash Worker-Entry aus @emdash-cms/cloudflare/worker:

export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";

Fügen Sie dann einen Cron Trigger in wrangler.jsonc hinzu:

{
	"triggers": {
		"crons": ["* * * * *"],
	},
}

Deployen

Auf Cloudflare Workers deployen:

wrangler deploy

Ihre Site ist jetzt live unter https://my-emdash-site.<your-subdomain>.workers.dev.

Read Replicas

Für global verteilte Sites aktivieren Sie die D1-Read-Replikation, um Leseabfragen an nahe Replicas weiterzuleiten, anstatt immer die primäre Datenbank abzufragen. Dies reduziert die Latenz für Besucher, die weit von der primären Region entfernt sind, erheblich.

emdash({
	database: d1({
		binding: "DB",
		session: "auto",
	}),
	storage: r2({ binding: "MEDIA" }),
}),

Sie müssen die Read-Replikation auch auf der D1-Datenbank selbst im Cloudflare-Dashboard oder über die REST-API aktivieren.

Siehe Datenbankoptionen — Read Replicas für Session-Modi und wie die Bookmark-basierte Konsistenz funktioniert.

Object Cache

Um die Leselast auf D1 zu reduzieren, cachen Sie Inhalts- und Konfigurationsabfrageergebnisse in Cloudflare KV. Lesezugriffe werden aus KV bedient, anstatt bei jeder Anfrage die Datenbank abzufragen:

import { d1, r2, kvCache } from "@emdash-cms/cloudflare";

emdash({
	database: d1({ binding: "DB" }),
	storage: r2({ binding: "MEDIA" }),
	objectCache: kvCache({ binding: "CACHE" }),
}),

Siehe Object Cache für KV-Setup, Optionen und Invalidierungsverhalten.

Workers Cache

Cloudflares Workers Cache ("cache": { "enabled": true } in wrangler.jsonc) stellt einen Edge-Cache vor Ihren Worker: Passende Anfragen werden bedient, ohne Ihren Worker auszuführen. Dies funktioniert gut mit EmDash:

  • EmDash-Admin- und API-Antworten senden Cache-Control: private, no-store und werden nie gespeichert.
  • Ihre öffentlichen Seiten steuern ihr eigenes Caching über die Cache-Control-Header, die sie zurückgeben.

Zwei Dinge, die Sie vor der Aktivierung wissen sollten:

  1. Antworten ohne Cache-Control-Header werden trotzdem gecacht. Workers Cache wendet RFC 9111 heuristische Frische an — ein 200 ohne Header wird 2 Stunden gecacht. Geben Sie jeder benutzerdefinierten Route einen expliziten Cache-Control (verwenden Sie private, no-store für alles Session-Abhängige).
  2. Gecachte Seiten werden mit eingeloggten Redakteuren geteilt. Der Cache läuft vor Ihrem Worker, sodass er nicht basierend auf Request-Cookies umgangen werden kann. Ein eingeloggter Redakteur erhält möglicherweise die gecachte anonyme Variante einer öffentlichen Seite — ohne die visuelle Bearbeitungsleiste — bis der Eintrag abläuft. Redakteur-gerenderte Antworten selbst werden nie gespeichert (sie tragen private, no-store), sodass nichts in die andere Richtung durchsickert.

Benutzerdefinierte Domain

Fügen Sie eine benutzerdefinierte Domain im Cloudflare-Dashboard hinzu:

  1. Gehen Sie zu Workers & Pages > Ihr Worker
  2. Klicken Sie auf Custom Domains > Add Custom Domain
  3. Geben Sie Ihre Domain ein und folgen Sie den DNS-Setup-Anweisungen

Öffentlicher R2-Zugriff

Um Medien direkt von R2 bereitzustellen (empfohlen für Performance):

  1. Gehen Sie im Cloudflare-Dashboard zu R2 > Ihr Bucket
  2. Klicken Sie auf Settings > Public access
  3. Aktivieren Sie den öffentlichen Zugriff und notieren Sie die öffentliche URL
  4. Aktualisieren Sie Ihre Speicherkonfiguration:
storage: r2({
  binding: "MEDIA",
  publicUrl: "https://pub-xxx.r2.dev"
}),

Cloudflare Access Authentifizierung

Wenn Ihre Organisation Cloudflare Access verwendet, können Sie es als Authentifizierungsanbieter anstelle von Passkeys verwenden und Single Sign-On über Ihren bestehenden Identitätsanbieter ermöglichen. Die folgende Konfiguration aktiviert dies:

emdash({
  database: d1({ binding: "DB" }),
  storage: r2({ binding: "MEDIA" }),
  auth: access({
    teamDomain: "myteam.cloudflareaccess.com",
    audience: "your-app-audience-tag",
    roleMapping: {
      "Admins": 50,
      "Editors": 40,
    },
  }),
}),

Siehe die Authentifizierungsanleitung für alle Konfigurationsoptionen.

E-Mail

Auf Workers ist der einzige eingebaute email:deliver-Handler ein Dev-Konsolen-Stub, sodass E-Mail-abhängige Flows — Magic-Link-Login, Team-Einladungen und Kommentar- Benachrichtigungen — in der Produktion mit “Email is not configured” fehlschlagen. Das cloudflareEmail()-Plugin liefert echte E-Mails über Cloudflare Email Sending mit einem nativen send_email Worker-Binding, ohne externe API-Schlüssel.

1. Absender-Domain einrichten

Gehen Sie im Cloudflare-Dashboard zu Email und verifizieren Sie die Domain (oder Adresse), von der Sie senden. Email Sending lehnt Nachrichten von unverifizierten Absendern ab.

2. Binding hinzufügen

Deklarieren Sie ein send_email-Binding in wrangler.jsonc:

{
	"send_email": [{ "name": "EMAIL" }],
}

3. Provider registrieren

Fügen Sie das Plugin zu Ihrer emdash()-Integration hinzu:

import { d1, r2 } from "@emdash-cms/cloudflare";
import { cloudflareEmail } from "@emdash-cms/cloudflare/plugins";

emdash({
	database: d1({ binding: "DB" }),
	storage: r2({ binding: "MEDIA" }),
	plugins: [
		cloudflareEmail({
			from: { email: "[email protected]", name: "My Site CMS" },
			replyTo: "[email protected]", // optional
			binding: "EMAIL", // optional, Standard ist "EMAIL"
		}),
	],
}),

4. Aktivieren und auswählen

Deployen Sie, aktivieren Sie dann das Plugin unter Admin → Extensions und wählen Sie es als Provider unter Settings → Email.

Optionen

OptionTypStandardBeschreibung
fromstring | { email, name? }— (erforderlich)Absenderadresse auf einer für Email Sending eingerichteten Domain.
replyTostringOptionale Reply-To-Adresse, nützlich wenn from eine No-Reply-Subdomain-Adresse ist.
bindingstring"EMAIL"Name des send_email-Bindings in wrangler.jsonc.

Umgebungsvariablen

Empfohlen: Verschlüsselungsschlüssel

EMDASH_ENCRYPTION_KEY ist der Schlüssel zur Verschlüsselung von Plugin-Secrets im Ruhezustand (Webhook-Tokens, Turnstile-Schlüssel usw.). Der Schlüssel wird beim Start validiert; die Plugin-Secret-Verschlüsselung verwendet ihn, sobald sie aktiviert ist. Setzen Sie ihn bei jedem Deployment, damit Secrets ohne spätere Konfigurationsänderung geschützt sind.

Der Schlüssel wird von Ihnen bereitgestellt und nie in der Datenbank gespeichert; nur verschlüsselter Chiffretext wird gespeichert. Seinen Verlust bedeutet den Verlust jedes mit ihm verschlüsselten Secrets.

Generieren Sie einen Schlüssel und speichern Sie ihn als Worker-Secret mit den folgenden Befehlen:

npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY

Optional: Überschreibungen stabiler Werte

EmDash generiert automatisch das Preview-HMAC-Secret und den Commenter-IP-Hash- Salt und speichert sie bei der ersten Verwendung in der Datenbank. Die untenstehenden Umgebungsvariablen sind Überschreibungen für Fälle, in denen Sie den Wert selbst fixieren müssen — zum Beispiel, wenn ein Preview-Worker in einem separaten Prozess das Secret mit Ihrer Hauptseite teilen muss.

VariableZweck
EMDASH_PREVIEW_SECRETÜberschreibung für das automatisch generierte Preview-HMAC-Secret.
EMDASH_IP_SALTÜberschreibung für den automatisch generierten Commenter-IP-Hash-Salt.
EMDASH_AUTH_SECRETOptional. Wenn gesetzt, wird es als IP-Salt-Quelle verwendet (es sei denn, EMDASH_IP_SALT ist auch gesetzt, was Vorrang hat), wodurch Commenter-IP-Hashes für Installationen stabil bleiben, die bereits darauf angewiesen sind. Lassen Sie es für ein neues Deployment ungesetzt.

Greifen Sie in Ihrer Konfiguration über import.meta.env oder das Cloudflare env-Binding auf Umgebungsvariablen zu.

Für das vollständige Inventar jedes Secrets, das EmDash verwendet — einschließlich Speicherorten, Rotationsschritten und was bei Verlust eines Schlüssels passiert — siehe Secrets & Schlüsselverwaltung.

Preview-Deployments

Deployen Sie einen Preview-Branch:

wrangler deploy --env preview

Fügen Sie einen Environment-Abschnitt in wrangler.jsonc hinzu:

{
	"env": {
		"preview": {
			"d1_databases": [
				{
					"binding": "DB",
					"database_name": "emdash-db-preview",
				},
			],
		},
	},
}

Fehlerbehebung

”D1 binding not found”

Überprüfen Sie, ob der Binding-Name in wrangler.jsonc mit Ihrer Datenbankkonfiguration übereinstimmt:

// Muss übereinstimmen: d1({ binding: "DB" })
"binding": "DB"

“R2 binding not found”

Prüfen Sie, ob der R2-Bucket korrekt gebunden ist:

// Muss übereinstimmen: r2({ binding: "MEDIA" })
"binding": "MEDIA"

Migrationsfehler

Wenn Sie Schema-Fehler sehen, verfolgen Sie die Worker-Logs (wrangler tail) und reproduzieren Sie den Fehler, um die zugrunde liegende Meldung zu erfassen — und erstellen Sie dann ein Issue mit dieser Ausgabe.