Deploy to Cloudflare
Cloudflare Workers provides a fast, globally distributed runtime for EmDash. This guide covers deploying with D1 for the database and R2 for media storage.
Prerequisites
Section titled “Prerequisites”- A Cloudflare account
- Wrangler CLI installed (
npm install -g wrangler) - Authenticated with Cloudflare (
wrangler login)
Create Resources
Section titled “Create Resources”1. Create a D1 database
Section titled “1. Create a D1 database”wrangler d1 create emdash-dbNote the database_id from the output.
2. Create an R2 bucket
Section titled “2. Create an R2 bucket”wrangler r2 bucket create emdash-media3. Create wrangler.jsonc
Section titled “3. Create wrangler.jsonc”Create wrangler.jsonc in your project root:
{ "$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", "database_id": "your-database-id", }, ],
"r2_buckets": [ { "binding": "MEDIA", "bucket_name": "emdash-media", }, ],}Configure EmDash
Section titled “Configure EmDash”Update your Astro configuration to use D1 and R2:
import { defineConfig } from "astro/config";import cloudflare from "@astrojs/cloudflare";import emdash, { r2 } from "emdash/astro";import { d1 } from "emdash/db";
export default defineConfig({ output: "server", adapter: cloudflare(), integrations: [ emdash({ database: d1({ binding: "DB" }), storage: r2({ binding: "MEDIA" }), }), ],});Run Migrations
Section titled “Run Migrations”Generate and apply the database schema.
1. Export the schema SQL
Section titled “1. Export the schema SQL”npx emdash init --database ./data.db2. Apply migrations to D1
Section titled “2. Apply migrations to D1”wrangler d1 migrations apply emdash-dbIf you don’t have migration files, apply the core schema directly:
wrangler d1 execute emdash-db --file=./node_modules/emdash/migrations/0001_core.sqlDeploy
Section titled “Deploy”Deploy to Cloudflare Workers:
wrangler deployYour site is now live at https://my-emdash-site.<your-subdomain>.workers.dev.
Read Replicas
Section titled “Read Replicas”For globally distributed sites, enable D1 read replication to route read queries to nearby replicas instead of always hitting the primary database. This significantly reduces latency for visitors far from the primary region.
emdash({ database: d1({ binding: "DB", session: "auto", }), storage: r2({ binding: "MEDIA" }),}),You also need to enable read replication on the D1 database itself in the Cloudflare dashboard or via the REST API.
See Database Options — Read Replicas for session modes and how bookmark-based consistency works.
Custom Domain
Section titled “Custom Domain”Add a custom domain in the Cloudflare dashboard:
- Go to Workers & Pages > your worker
- Click Custom Domains > Add Custom Domain
- Enter your domain and follow the DNS setup instructions
Public R2 Access
Section titled “Public R2 Access”To serve media directly from R2 (recommended for performance):
- In the Cloudflare dashboard, go to R2 > your bucket
- Click Settings > Public access
- Enable public access and note the public URL
- Update your storage config:
storage: r2({ binding: "MEDIA", publicUrl: "https://pub-xxx.r2.dev"}),Cloudflare Access Authentication
Section titled “Cloudflare Access Authentication”If your organization uses Cloudflare Access, you can use it as your authentication provider instead of passkeys. This provides SSO with your existing identity provider.
emdash({ database: d1({ binding: "DB" }), storage: r2({ binding: "MEDIA" }), auth: { cloudflareAccess: { teamDomain: "myteam.cloudflareaccess.com", audience: "your-app-audience-tag", roleMapping: { "Admins": 50, "Editors": 40, }, }, },}),See the Authentication guide for full configuration options.
Environment Variables
Section titled “Environment Variables”EmDash requires certain secrets for authentication and preview functionality.
Required Secrets
Section titled “Required Secrets”| Variable | Purpose |
|---|---|
EMDASH_AUTH_SECRET | Signs session cookies and auth tokens. Required for production. |
EMDASH_PREVIEW_SECRET | Signs preview URLs for draft content. Required for preview functionality. |
Generate secure secrets:
npx emdash auth secretSet secrets via Wrangler:
wrangler secret put EMDASH_AUTH_SECRETwrangler secret put EMDASH_PREVIEW_SECRETAccess environment variables in your configuration using import.meta.env or the Cloudflare env binding.
Preview Deployments
Section titled “Preview Deployments”Deploy a preview branch:
wrangler deploy --env previewAdd an environment section to wrangler.jsonc:
{ "env": { "preview": { "d1_databases": [ { "binding": "DB", "database_name": "emdash-db-preview", "database_id": "your-preview-db-id", }, ], }, },}Troubleshooting
Section titled “Troubleshooting””D1 binding not found”
Section titled “”D1 binding not found””Verify the binding name in wrangler.jsonc matches your database configuration:
// Must match: d1({ binding: "DB" })"binding": "DB"“R2 binding not found”
Section titled ““R2 binding not found””Check that the R2 bucket is correctly bound:
// Must match: r2({ binding: "MEDIA" })"binding": "MEDIA"Migration errors
Section titled “Migration errors”D1 migrations run via Wrangler, not at runtime. If you see schema errors:
- Check that migrations were applied:
wrangler d1 migrations list emdash-db - Re-apply if needed:
wrangler d1 migrations apply emdash-db