Desplegar en Cloudflare

En esta página

Cloudflare Workers proporciona un runtime rápido y distribuido globalmente para EmDash. Esta guía cubre el despliegue con D1 para la base de datos y R2 para el almacenamiento de medios.

Requisitos previos

  • Una cuenta de Cloudflare
  • Wrangler CLI instalado (npm install -g wrangler)
  • Autenticado con Cloudflare (wrangler login)

Configurar Bindings

Crea wrangler.jsonc en la raíz de tu proyecto con bindings de D1 y R2. Wrangler aprovisiona ambos recursos en el primer despliegue si aún no existen.

{
	"$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",
		},
	],
}

Configurar EmDash

Actualiza tu configuración de Astro para usar D1 y 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(), // Requerido — la interfaz admin es una app React
		emdash({
			database: d1({ binding: "DB" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Primer arranque

Las migraciones de base de datos se ejecutan automáticamente en la primera solicitud después del despliegue, y en cada arranque posterior si hay algo nuevo que aplicar.

Si la base de datos está vacía (sin colecciones) y el asistente de configuración no se ha completado, EmDash también aplica un archivo seed en el primer arranque. El seed se lee en tiempo de compilación desde .emdash/seed.json, la ruta en package.json#emdash.seed o seed/seed.json — lo que se encuentre primero — y se incluye en el bundle. Si no hay ninguno presente, se usa un seed predeterminado integrado. Los despliegues posteriores contra una base de datos existente no modifican su contenido.

Para cambiar el esquema o modelo de contenido de un sitio ya desplegado, consulta Evolucionar un sitio desplegado.

Publicación programada

En Cloudflare Workers, la publicación programada, los cron de plugins y las tareas de mantenimiento se ejecutan desde un Worker Cron Trigger. Las nuevas plantillas de Cloudflare incluyen esta configuración automáticamente. Si estás actualizando un proyecto existente, exporta la entrada del Worker EmDash desde @emdash-cms/cloudflare/worker:

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

Luego añade un Cron Trigger a wrangler.jsonc:

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

Desplegar

Despliega en Cloudflare Workers:

wrangler deploy

Tu sitio está ahora en vivo en https://my-emdash-site.<your-subdomain>.workers.dev.

Read Replicas

Para sitios distribuidos globalmente, habilita la replicación de lectura de D1 para enrutar las consultas de lectura a réplicas cercanas en lugar de consultar siempre la base de datos primaria. Esto reduce significativamente la latencia para visitantes lejanos de la región primaria.

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

También necesitas habilitar la replicación de lectura en la propia base de datos D1 desde el panel de Cloudflare o mediante la API REST.

Consulta Opciones de base de datos — Read Replicas para los modos de sesión y cómo funciona la consistencia basada en marcadores.

Object Cache

Para reducir la carga de lectura en D1, almacena en caché los resultados de consultas de contenido y configuración en Cloudflare KV. Las lecturas se sirven desde KV en lugar de consultar la base de datos en cada solicitud:

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

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

Consulta Object Cache para la configuración de KV, opciones y comportamiento de invalidación.

Workers Cache

El Workers Cache de Cloudflare ("cache": { "enabled": true } en wrangler.jsonc) coloca una caché edge delante de tu Worker: las solicitudes coincidentes se sirven sin ejecutar tu Worker en absoluto. Esto funciona bien con EmDash:

  • Las respuestas del admin y API de EmDash envían Cache-Control: private, no-store y nunca se almacenan.
  • Tus páginas públicas controlan su propio almacenamiento en caché a través de los encabezados Cache-Control que devuelven.

Dos cosas a saber antes de habilitarlo:

  1. Las respuestas sin encabezado Cache-Control también se almacenan en caché. Workers Cache aplica la frescura heurística RFC 9111 — un 200 sin ningún encabezado se almacena en caché durante 2 horas. Dale a cada ruta personalizada un Cache-Control explícito (usa private, no-store para cualquier cosa dependiente de sesión).
  2. Las páginas en caché se comparten con editores autenticados. La caché se ejecuta antes de tu Worker, por lo que no puede eludirse basándose en cookies de solicitud. Un editor autenticado puede recibir la variante anónima en caché de una página pública — sin la barra de herramientas de edición visual — hasta que la entrada expire. Las respuestas renderizadas por editores nunca se almacenan (llevan private, no-store), por lo que nada se filtra en la otra dirección.

Dominio personalizado

Añade un dominio personalizado en el panel de Cloudflare:

  1. Ve a Workers & Pages > tu worker
  2. Haz clic en Custom Domains > Add Custom Domain
  3. Introduce tu dominio y sigue las instrucciones de configuración DNS

Acceso público a R2

Para servir medios directamente desde R2 (recomendado para rendimiento):

  1. En el panel de Cloudflare, ve a R2 > tu bucket
  2. Haz clic en Settings > Public access
  3. Habilita el acceso público y anota la URL pública
  4. Actualiza tu configuración de almacenamiento:
storage: r2({
  binding: "MEDIA",
  publicUrl: "https://pub-xxx.r2.dev"
}),

Autenticación con Cloudflare Access

Si tu organización usa Cloudflare Access, puedes usarlo como proveedor de autenticación en lugar de passkeys, proporcionando inicio de sesión único a través de tu proveedor de identidad existente. La siguiente configuración lo habilita:

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,
    },
  }),
}),

Consulta la guía de autenticación para todas las opciones de configuración.

Email

En Workers, el único manejador integrado de email:deliver es un stub de consola de desarrollo, por lo que los flujos dependientes de email — inicio de sesión por magic-link, invitaciones de equipo y notificaciones de comentarios — fallan con “Email is not configured” en producción. El plugin cloudflareEmail() envía emails reales a través de Cloudflare Email Sending usando un binding nativo de Worker send_email, sin claves API externas.

1. Registrar un dominio de envío

En el panel de Cloudflare, ve a Email y verifica el dominio (o dirección) desde el que envías. Email Sending rechaza mensajes de remitentes no verificados.

2. Añadir el binding

Declara un binding send_email en wrangler.jsonc:

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

3. Registrar el proveedor

Añade el plugin a tu integración emdash():

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]", // opcional
			binding: "EMAIL", // opcional, por defecto "EMAIL"
		}),
	],
}),

4. Activar y seleccionar

Despliega, luego activa el plugin en Admin → Extensions y elígelo como proveedor en Settings → Email.

Opciones

OpciónTipoPredeterminadoDescripción
fromstring | { email, name? }— (requerido)Dirección del remitente en un dominio registrado para Email Sending.
replyTostringReply-To opcional, útil cuando from es una dirección de subdominio no-reply.
bindingstring"EMAIL"Nombre del binding send_email en wrangler.jsonc.

Variables de entorno

Recomendado: clave de cifrado

EMDASH_ENCRYPTION_KEY es la clave para cifrar los secrets de plugins en reposo (tokens de webhook, claves de Turnstile, etc.). La clave se valida al iniciar; el cifrado de secrets de plugins la usa una vez habilitado. Configúrala en cada despliegue para que los secrets estén protegidos sin un cambio de configuración posterior.

La clave la proporcionas tú y nunca se almacena en la base de datos; solo se almacena texto cifrado. Perderla significa perder cada secret cifrado con ella.

Genera una clave y almacénala como secret del Worker con los siguientes comandos:

npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY

Opcional: sobrescrituras de valores estables

EmDash auto-genera el secret HMAC de preview y el salt de hash de IP de comentaristas y los persiste en la base de datos en el primer uso. Las variables de entorno a continuación son sobrescrituras para casos donde necesitas fijar el valor tú mismo — por ejemplo, cuando un Worker de preview en un proceso separado necesita compartir el secret con tu sitio principal.

VariablePropósito
EMDASH_PREVIEW_SECRETSobrescritura para el secret HMAC de preview auto-generado.
EMDASH_IP_SALTSobrescritura para el salt de hash de IP de comentaristas auto-generado.
EMDASH_AUTH_SECRETOpcional. Si se establece, se usa como fuente de salt de IP (a menos que EMDASH_IP_SALT también esté establecido, que tiene precedencia), manteniendo los hashes de IP de comentaristas estables para instalaciones que ya dependen de él. Déjalo sin establecer para un nuevo despliegue.

Accede a las variables de entorno en tu configuración usando import.meta.env o el binding env de Cloudflare.

Para el inventario completo de cada secret que EmDash usa — incluyendo ubicaciones de almacenamiento, pasos de rotación y qué se rompe cuando se pierde una clave — consulta Secrets y gestión de claves.

Despliegues de preview

Despliega una rama de preview:

wrangler deploy --env preview

Añade una sección de entorno a wrangler.jsonc:

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

Solución de problemas

”D1 binding not found”

Verifica que el nombre del binding en wrangler.jsonc coincida con tu configuración de base de datos:

// Debe coincidir: d1({ binding: "DB" })
"binding": "DB"

“R2 binding not found”

Comprueba que el bucket R2 esté correctamente vinculado:

// Debe coincidir: r2({ binding: "MEDIA" })
"binding": "MEDIA"

Errores de migración

Si ves errores de esquema, sigue los logs del Worker (wrangler tail) y reproduce el error para capturar el mensaje subyacente — luego abre un issue con esa salida.