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-storeund 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:
- Antworten ohne
Cache-Control-Header werden trotzdem gecacht. Workers Cache wendet RFC 9111 heuristische Frische an — ein200ohne Header wird 2 Stunden gecacht. Geben Sie jeder benutzerdefinierten Route einen explizitenCache-Control(verwenden Sieprivate, no-storefür alles Session-Abhängige). - 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:
- Gehen Sie zu Workers & Pages > Ihr Worker
- Klicken Sie auf Custom Domains > Add Custom Domain
- 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):
- Gehen Sie im Cloudflare-Dashboard zu R2 > Ihr Bucket
- Klicken Sie auf Settings > Public access
- Aktivieren Sie den öffentlichen Zugriff und notieren Sie die öffentliche URL
- 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.
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
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
from | string | { email, name? } | — (erforderlich) | Absenderadresse auf einer für Email Sending eingerichteten Domain. |
replyTo | string | — | Optionale Reply-To-Adresse, nützlich wenn from eine No-Reply-Subdomain-Adresse ist. |
binding | string | "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.
| Variable | Zweck |
|---|---|
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_SECRET | Optional. 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.