Deploy no Cloudflare

Nesta página

Cloudflare Workers fornece um runtime rápido e distribuído globalmente para EmDash. Este guia cobre o deploy com D1 para o banco de dados e R2 para armazenamento de mídia.

Pré-requisitos

  • Uma conta Cloudflare
  • Wrangler CLI instalado (npm install -g wrangler)
  • Autenticado no Cloudflare (wrangler login)

Configurar bindings

Crie wrangler.jsonc na raiz do seu projeto com bindings D1 e R2. O Wrangler provisiona ambos os recursos no primeiro deploy se eles ainda não existirem.

{
	"$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

Atualize sua configuração Astro para usar D1 e 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(), // Obrigatório — a interface admin é uma app React
		emdash({
			database: d1({ binding: "DB" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Primeiro boot

As migrações do banco de dados são executadas automaticamente na primeira requisição após o deploy, e em cada boot subsequente se houver algo novo para aplicar.

Se o banco de dados estiver vazio (sem coleções) e o assistente de configuração não tiver sido concluído, o EmDash também aplica um arquivo seed no primeiro boot. O seed é lido em tempo de build a partir de .emdash/seed.json, do caminho em package.json#emdash.seed ou de seed/seed.json — o que for encontrado primeiro — e é incluído no bundle. Se nenhum estiver presente, um seed padrão embutido é usado. Deploys subsequentes em um banco de dados existente não alteram seu conteúdo.

Para alterar o schema ou modelo de conteúdo de um site já implantado, consulte Evoluindo um site implantado.

Publicação agendada

No Cloudflare Workers, publicação agendada, cron de plugins e tarefas de manutenção são executados a partir de um Worker Cron Trigger. Novos templates do Cloudflare incluem essa configuração automaticamente. Se você está atualizando um projeto existente, exporte a entrada do Worker EmDash de @emdash-cms/cloudflare/worker:

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

Em seguida, adicione um Cron Trigger ao wrangler.jsonc:

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

Deploy

Faça deploy no Cloudflare Workers:

wrangler deploy

Seu site está agora ativo em https://my-emdash-site.<your-subdomain>.workers.dev.

Read Replicas

Para sites distribuídos globalmente, habilite a replicação de leitura do D1 para rotear consultas de leitura para réplicas próximas em vez de sempre acessar o banco de dados primário. Isso reduz significativamente a latência para visitantes distantes da região primária.

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

Você também precisa habilitar a replicação de leitura no próprio banco de dados D1 no painel do Cloudflare ou via REST API.

Consulte Opções de banco de dados — Read Replicas para modos de sessão e como a consistência baseada em bookmarks funciona.

Object Cache

Para reduzir a carga de leitura no D1, faça cache dos resultados de consultas de conteúdo e configuração no Cloudflare KV. As leituras são servidas do KV em vez de consultar o banco de dados em cada requisição:

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

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

Consulte Object Cache para configuração do KV, opções e comportamento de invalidação.

Workers Cache

O Workers Cache do Cloudflare ("cache": { "enabled": true } no wrangler.jsonc) coloca um cache de borda na frente do seu Worker: requisições correspondentes são servidas sem executar seu Worker. Isso funciona bem com EmDash:

  • As respostas admin e API do EmDash enviam Cache-Control: private, no-store e nunca são armazenadas.
  • Suas páginas públicas controlam seu próprio cache através dos cabeçalhos Cache-Control que retornam.

Duas coisas a saber antes de habilitar:

  1. Respostas sem cabeçalho Cache-Control ainda são cacheadas. Workers Cache aplica frescor heurístico RFC 9111 — um 200 sem nenhum cabeçalho é cacheado por 2 horas. Dê a cada rota personalizada um Cache-Control explícito (use private, no-store para qualquer coisa dependente de sessão).
  2. Páginas cacheadas são compartilhadas com editores logados. O cache roda antes do seu Worker, então não pode ser contornado com base em cookies da requisição. Um editor logado pode receber a variante anônima cacheada de uma página pública — sem a barra de ferramentas de edição visual — até que a entrada expire. Respostas renderizadas por editores nunca são armazenadas (possuem private, no-store), então nada vaza na outra direção.

Domínio personalizado

Adicione um domínio personalizado no painel do Cloudflare:

  1. Vá para Workers & Pages > seu worker
  2. Clique em Custom Domains > Add Custom Domain
  3. Insira seu domínio e siga as instruções de configuração DNS

Acesso público ao R2

Para servir mídia diretamente do R2 (recomendado para performance):

  1. No painel do Cloudflare, vá para R2 > seu bucket
  2. Clique em Settings > Public access
  3. Habilite o acesso público e anote a URL pública
  4. Atualize sua configuração de armazenamento:
storage: r2({
  binding: "MEDIA",
  publicUrl: "https://pub-xxx.r2.dev"
}),

Autenticação Cloudflare Access

Se sua organização usa Cloudflare Access, você pode usá-lo como provedor de autenticação em vez de passkeys, fornecendo single sign-on através do seu provedor de identidade existente. A seguinte configuração o 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,
    },
  }),
}),

Consulte o guia de autenticação para todas as opções de configuração.

Email

No Workers, o único handler embutido email:deliver é um stub de console de desenvolvimento, então fluxos dependentes de email — login por magic-link, convites de equipe e notificações de comentários — falham com “Email is not configured” em produção. O plugin cloudflareEmail() entrega emails reais através do Cloudflare Email Sending usando um binding Worker nativo send_email, sem chaves API externas.

1. Registrar um domínio de envio

No painel do Cloudflare, vá para Email e verifique o domínio (ou endereço) de onde você envia. Email Sending rejeita mensagens de remetentes não verificados.

2. Adicionar o binding

Declare um binding send_email no wrangler.jsonc:

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

3. Registrar o provedor

Adicione o plugin à sua integração 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, padrão "EMAIL"
		}),
	],
}),

4. Ativar e selecionar

Faça deploy, então ative o plugin em Admin → Extensions e escolha-o como provedor em Settings → Email.

Opções

OpçãoTipoPadrãoDescrição
fromstring | { email, name? }— (obrigatório)Endereço do remetente em um domínio registrado para Email Sending.
replyTostringReply-To opcional, útil quando from é um endereço de subdomínio no-reply.
bindingstring"EMAIL"Nome do binding send_email no wrangler.jsonc.

Variáveis de ambiente

Recomendado: chave de criptografia

EMDASH_ENCRYPTION_KEY é a chave para criptografar secrets de plugins em repouso (tokens de webhook, chaves Turnstile, etc.). A chave é validada na inicialização; a criptografia de secrets de plugins a usa quando habilitada. Configure-a em cada deploy para que os secrets sejam protegidos sem uma alteração de configuração posterior.

A chave é fornecida por você e nunca armazenada no banco de dados; apenas texto cifrado criptografado é armazenado. Perdê-la significa perder cada secret criptografado com ela.

Gere uma chave e armazene-a como secret do Worker com os seguintes comandos:

npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY

Opcional: sobrescritas de valores estáveis

EmDash gera automaticamente o secret HMAC de preview e o salt de hash de IP de comentaristas e os persiste no banco de dados no primeiro uso. As variáveis de ambiente abaixo são sobrescritas para casos onde você precisa fixar o valor você mesmo — por exemplo, quando um Worker de preview em um processo separado precisa compartilhar o secret com seu site principal.

VariávelPropósito
EMDASH_PREVIEW_SECRETSobrescrita para o secret HMAC de preview auto-gerado.
EMDASH_IP_SALTSobrescrita para o salt de hash de IP de comentaristas auto-gerado.
EMDASH_AUTH_SECRETOpcional. Se definido, é usado como fonte de salt de IP (a menos que EMDASH_IP_SALT também esteja definido, que tem precedência), mantendo os hashes de IP de comentaristas estáveis para instalações que já dependem dele. Deixe não definido para um novo deploy.

Acesse variáveis de ambiente na sua configuração usando import.meta.env ou o binding env do Cloudflare.

Para o inventário completo de cada secret que o EmDash usa — incluindo locais de armazenamento, etapas de rotação e o que quebra quando uma chave é perdida — consulte Secrets e gerenciamento de chaves.

Deploys de preview

Faça deploy de uma branch de preview:

wrangler deploy --env preview

Adicione uma seção de ambiente ao wrangler.jsonc:

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

Resolução de problemas

”D1 binding not found”

Verifique se o nome do binding no wrangler.jsonc corresponde à sua configuração de banco de dados:

// Deve corresponder: d1({ binding: "DB" })
"binding": "DB"

“R2 binding not found”

Verifique se o bucket R2 está corretamente vinculado:

// Deve corresponder: r2({ binding: "MEDIA" })
"binding": "MEDIA"

Erros de migração

Se você vir erros de schema, acompanhe os logs do Worker (wrangler tail) e reproduza o erro para capturar a mensagem subjacente — depois abra uma issue com essa saída.