Cloudflare Workers proporciona un entorno de ejecución 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 emdash from "emdash/astro";
import { d1, r2 } from "@emdash-cms/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [
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 dejan su contenido intacto.
Desplegar
Despliega en Cloudflare Workers:
wrangler deploy
Tu sitio ahora está 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 consultas de lectura a réplicas cercanas en lugar de siempre acceder a la base de datos principal. Esto reduce significativamente la latencia para visitantes lejos de la región principal.
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 en 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.
Dominio Personalizado
Añade un dominio personalizado en el panel de Cloudflare:
- Ve a Workers & Pages > tu worker
- Haz clic en Custom Domains > Add Custom Domain
- Ingresa tu dominio y sigue las instrucciones de configuración de DNS
Acceso Público a R2
Para servir medios directamente desde R2 (recomendado para rendimiento):
- En el panel de Cloudflare, ve a R2 > tu bucket
- Haz clic en Settings > Public access
- Habilita el acceso público y anota la URL pública
- 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 opciones de configuración completas.
Variables de Entorno
Recomendado: clave de cifrado
EMDASH_ENCRYPTION_KEY es la clave para cifrar secretos de plugins en reposo (tokens de webhook, claves de Turnstile, etc.). La clave se valida al inicio; el cifrado de secretos de plugins la usa una vez habilitado. Configúrala en cada despliegue para que los secretos estén protegidos sin un cambio de configuración posterior.
La clave es proporcionada por ti y nunca se almacena en la base de datos; solo se almacena texto cifrado. Perderla significa perder cada secreto cifrado con ella.
Genera una clave y almacénala como un secreto de Worker con los siguientes comandos:
npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY
Opcional: sobrescrituras de valores estables
EmDash genera automáticamente el secreto HMAC de vista previa y el salt de hash de IP de comentarista 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 vista previa en un proceso separado necesita compartir el secreto con tu sitio principal.
| Variable | Propósito |
|---|---|
EMDASH_PREVIEW_SECRET | Sobrescritura para el secreto HMAC de vista previa generado automáticamente. |
EMDASH_IP_SALT | Sobrescritura para el salt de hash de IP de comentarista generado automáticamente. |
EMDASH_AUTH_SECRET | Opcional. 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 estables los hashes de IP de comentarista 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.
Despliegues de Vista Previa
Despliega una rama de vista previa:
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:
// Must match: d1({ binding: "DB" })
"binding": "DB"
“R2 binding not found”
Verifica que el bucket de R2 esté correctamente vinculado:
// Must match: 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.