Deploy su Cloudflare

In questa pagina

Cloudflare Workers fornisce un runtime veloce e distribuito globalmente per EmDash. Questa guida copre il deployment con D1 per il database e R2 per lo storage dei media.

Prerequisiti

  • Un account Cloudflare
  • Wrangler CLI installato (npm install -g wrangler)
  • Autenticato con Cloudflare (wrangler login)

Configurare i binding

Crea wrangler.jsonc nella root del tuo progetto con i binding D1 e R2. Wrangler provisiona entrambe le risorse al primo deploy se non esistono ancora.

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

Configurare EmDash

Aggiorna la tua configurazione Astro per usare 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(), // Richiesto — l'interfaccia admin è un'app React
		emdash({
			database: d1({ binding: "DB" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Primo avvio

Le migrazioni del database vengono eseguite automaticamente alla prima richiesta dopo il deployment, e ad ogni avvio successivo se ci sono nuove migrazioni da applicare.

Se il database è vuoto (nessuna collezione) e la procedura guidata di configurazione non è stata completata, EmDash applica anche un file seed al primo avvio. Il seed viene letto al momento del build da .emdash/seed.json, dal percorso in package.json#emdash.seed o da seed/seed.json — qualunque venga trovato per primo — e incorporato nel bundle. Se nessuno è presente, viene usato un seed predefinito integrato. I deploy successivi su un database esistente non ne modificano il contenuto.

Per modificare lo schema o il modello di contenuto di un sito già deployato, consulta Evolvere un sito deployato.

Pubblicazione programmata

Su Cloudflare Workers, la pubblicazione programmata, i cron dei plugin e le attività di manutenzione vengono eseguiti da un Worker Cron Trigger. I nuovi template Cloudflare includono questa configurazione automaticamente. Se stai aggiornando un progetto esistente, esporta l’entry del Worker EmDash da @emdash-cms/cloudflare/worker:

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

Poi aggiungi un Cron Trigger a wrangler.jsonc:

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

Deployare

Deploya su Cloudflare Workers:

wrangler deploy

Il tuo sito è ora live su https://my-emdash-site.<your-subdomain>.workers.dev.

Read Replica

Per i siti distribuiti globalmente, abilita la replica di lettura D1 per instradare le query di lettura verso repliche vicine invece di interrogare sempre il database primario. Questo riduce significativamente la latenza per i visitatori lontani dalla regione primaria.

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

Devi anche abilitare la replica di lettura sul database D1 stesso dalla dashboard Cloudflare o tramite l’API REST.

Consulta Opzioni database — Read Replica per le modalità di sessione e come funziona la coerenza basata sui bookmark.

Object Cache

Per ridurre il carico di lettura su D1, memorizza nella cache i risultati delle query di contenuto e configurazione in Cloudflare KV. Le letture vengono servite da KV invece di interrogare il database ad ogni richiesta:

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

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

Consulta Object Cache per la configurazione KV, le opzioni e il comportamento di invalidazione.

Workers Cache

Il Workers Cache di Cloudflare ("cache": { "enabled": true } in wrangler.jsonc) posiziona una cache edge davanti al tuo Worker: le richieste corrispondenti vengono servite senza eseguire affatto il tuo Worker. Questo funziona bene con EmDash:

  • Le risposte admin e API di EmDash inviano Cache-Control: private, no-store e non vengono mai memorizzate.
  • Le tue pagine pubbliche controllano la propria cache attraverso gli header Cache-Control che restituiscono.

Due cose da sapere prima di abilitarlo:

  1. Le risposte senza header Cache-Control vengono comunque memorizzate nella cache. Workers Cache applica la freschezza euristica RFC 9111 — un 200 senza alcun header viene memorizzato per 2 ore. Dai a ogni route personalizzata un Cache-Control esplicito (usa private, no-store per qualsiasi cosa dipendente dalla sessione).
  2. Le pagine in cache sono condivise con gli editor autenticati. La cache viene eseguita prima del tuo Worker, quindi non può essere aggirata in base ai cookie della richiesta. Un editor autenticato potrebbe ricevere la variante anonima in cache di una pagina pubblica — senza la toolbar di editing visuale — fino alla scadenza della voce. Le risposte renderizzate dagli editor non vengono mai memorizzate (portano private, no-store), quindi nulla filtra nell’altra direzione.

Dominio personalizzato

Aggiungi un dominio personalizzato dalla dashboard Cloudflare:

  1. Vai su Workers & Pages > il tuo worker
  2. Clicca su Custom Domains > Add Custom Domain
  3. Inserisci il tuo dominio e segui le istruzioni per la configurazione DNS

Accesso pubblico R2

Per servire i media direttamente da R2 (consigliato per le prestazioni):

  1. Nella dashboard Cloudflare, vai su R2 > il tuo bucket
  2. Clicca su Settings > Public access
  3. Abilita l’accesso pubblico e prendi nota dell’URL pubblico
  4. Aggiorna la tua configurazione di storage:
storage: r2({
  binding: "MEDIA",
  publicUrl: "https://pub-xxx.r2.dev"
}),

Autenticazione Cloudflare Access

Se la tua organizzazione usa Cloudflare Access, puoi usarlo come provider di autenticazione al posto dei passkey, fornendo single sign-on attraverso il tuo provider di identità esistente. La seguente configurazione lo abilita:

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 guida all’autenticazione per tutte le opzioni di configurazione.

Email

Su Workers, l’unico handler integrato email:deliver è uno stub della console di sviluppo, quindi i flussi dipendenti dall’email — login con magic-link, inviti al team e notifiche dei commenti — falliscono con “Email is not configured” in produzione. Il plugin cloudflareEmail() invia email reali tramite Cloudflare Email Sending usando un binding Worker nativo send_email, senza chiavi API esterne.

1. Registrare un dominio mittente

Nella dashboard Cloudflare, vai su Email e verifica il dominio (o indirizzo) da cui invii. Email Sending rifiuta i messaggi da mittenti non verificati.

2. Aggiungere il binding

Dichiara un binding send_email in wrangler.jsonc:

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

3. Registrare il provider

Aggiungi il plugin alla tua integrazione 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]", // opzionale
			binding: "EMAIL", // opzionale, predefinito "EMAIL"
		}),
	],
}),

4. Attivare e selezionare

Deploya, poi attiva il plugin sotto Admin → Extensions e sceglilo come provider sotto Settings → Email.

Opzioni

OpzioneTipoPredefinitoDescrizione
fromstring | { email, name? }— (richiesto)Indirizzo mittente su un dominio registrato per Email Sending.
replyTostringReply-To opzionale, utile quando from è un indirizzo di sottodominio no-reply.
bindingstring"EMAIL"Nome del binding send_email in wrangler.jsonc.

Variabili d’ambiente

Consigliato: chiave di crittografia

EMDASH_ENCRYPTION_KEY è la chiave per crittografare i secret dei plugin a riposo (token webhook, chiavi Turnstile, ecc.). La chiave viene validata all’ avvio; la crittografia dei secret dei plugin la usa una volta abilitata. Impostala ad ogni deployment così i secret sono protetti senza un successivo cambio di configurazione.

La chiave è fornita da te e mai memorizzata nel database; solo il testo cifrato viene memorizzato. Perderla significa perdere ogni secret crittografato con essa.

Genera una chiave e memorizzala come secret del Worker con i seguenti comandi:

npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY

Opzionale: override di valori stabili

EmDash genera automaticamente il secret HMAC di preview e il salt dell’hash IP dei commentatori e li persiste nel database al primo utilizzo. Le variabili d’ambiente sotto sono override per i casi in cui devi fissare il valore tu stesso — ad esempio, quando un Worker di preview in un processo separato deve condividere il secret con il tuo sito principale.

VariabileScopo
EMDASH_PREVIEW_SECRETOverride per il secret HMAC di preview auto-generato.
EMDASH_IP_SALTOverride per il salt dell’hash IP dei commentatori auto-generato.
EMDASH_AUTH_SECRETOpzionale. Se impostato, viene usato come fonte del salt IP (a meno che anche EMDASH_IP_SALT sia impostato, che ha la precedenza), mantenendo gli hash IP dei commentatori stabili per le installazioni che già ne dipendono. Lascialo non impostato per un nuovo deployment.

Accedi alle variabili d’ambiente nella tua configurazione usando import.meta.env o il binding env di Cloudflare.

Per l’inventario completo di ogni secret che EmDash usa — incluse le posizioni di archiviazione, i passaggi di rotazione e cosa si rompe quando una chiave viene persa — consulta Secret e gestione delle chiavi.

Deployment di preview

Deploya un branch di preview:

wrangler deploy --env preview

Aggiungi una sezione ambiente a wrangler.jsonc:

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

Risoluzione dei problemi

”D1 binding not found”

Verifica che il nome del binding in wrangler.jsonc corrisponda alla tua configurazione del database:

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

“R2 binding not found”

Controlla che il bucket R2 sia correttamente collegato:

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

Errori di migrazione

Se vedi errori di schema, segui i log del Worker (wrangler tail) e riproduci l’errore per catturare il messaggio sottostante — poi apri una issue con quell’output.