Instalación
Requisitos
Sección titulada «Requisitos»- Node 24 o superior.
- Una cuenta de Cloudflare.
- Un proyecto Astro con el adaptador de Cloudflare. Si no lo tienes, el primer paso lo crea.
- Un token de GitHub con permiso
read:packages— el paquete se publica en GitHub Packages, y GitHub Packages exige autenticación también para leer, aunque el paquete sea público. Se genera en Settings → Developer settings → Personal access tokens.
Crea el proyecto
Sección titulada «Crea el proyecto»-
Crea la aplicación con la CLI de Cloudflare, eligiendo Astro y la plantilla en blanco:
Ventana de terminal pnpm create cloudflare@latest mi-sitio --framework=astro -
Añade React, que es lo que usa el administrador:
Ventana de terminal pnpm astro add react -
Apunta el scope
@kevolution-coa GitHub Packages, con un.npmrcen la raíz del proyecto:.npmrc @kevolution-co:registry=https://npm.pkg.github.com//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}${GITHUB_TOKEN}lo expande npm desde tu entorno al leer el fichero, así que el token no queda escrito en el repositorio. Sin este paso eladddel punto siguiente falla con un 401. -
Exporta el token e instala el CMS:
Ventana de terminal export GITHUB_TOKEN=ghp_…pnpm add @kevolution-co/cms
Crea los recursos de Cloudflare
Sección titulada «Crea los recursos de Cloudflare»Cada comando imprime un identificador que hay que pegar en wrangler.jsonc.
npx wrangler d1 create mi-sitionpx wrangler r2 bucket create mi-sitionpx wrangler kv namespace create KVnpx wrangler kv namespace create SESSION{ "compatibility_flags": ["nodejs_compat"], "d1_databases": [ { "binding": "DB", "database_name": "mi-sitio", "database_id": "<id>", "migrations_dir": "migrations", "migrations_pattern": "migrations/*/migration.sql" } ], "r2_buckets": [{ "binding": "R2", "bucket_name": "mi-sitio" }], "kv_namespaces": [ { "binding": "SESSION", "id": "<id>" }, { "binding": "KV", "id": "<id>" } ], "send_email": [{ "name": "EMAIL" }]}Y el secreto con el que se firman las sesiones, que no es un binding sino una variable:
openssl rand -base64 32BETTER_AUTH_SECRET=el-valor-que-acabas-de-generarEn producción, npx wrangler secret put BETTER_AUTH_SECRET. .dev.vars va en el .gitignore. Los
detalles, en Autenticación.
Cómo se leen los bindings
Sección titulada «Cómo se leen los bindings»Desde Astro 7 y @astrojs/cloudflare 14, Astro.locals.runtime.env ya no existe. Los bindings se importan:
import { env } from 'cloudflare:workers'El CMS lo hace por ti; solo lo necesitas si quieres acceder a un binding directamente.
Configura el email
Sección titulada «Configura el email»Enviar correo transaccional —verificación de cuenta, recuperación de contraseña— exige tres cosas:
- Un plan Workers de pago. Email Sending no está en el gratuito.
- Un dominio que use DNS de Cloudflare, dado de alta en Compute → Email Service → Email Sending.
- Que la dirección remitente pertenezca a ese dominio.
Y un bloque email en tu config, con esa dirección:
import { defineConfig } from '@kevolution-co/cms'
export default defineConfig({ email: { from: 'noreply@tudominio.com', siteName: 'Mi sitio' }, collections: [],})Este bloque son los valores de arranque. Los mismos tres se pueden cambiar después sin desplegar desde los ajustes del sitio, que viven en KV.
En desarrollo local no hace falta nada de esto: wrangler dev simula el envío y vuelca el contenido del
mensaje a un fichero cuya ruta imprime en la consola. Un from cualquiera vale mientras no sea vacío.
Si añades "remote": true al binding pierdes esa simulación y empiezas a enviar correo real desde tu
máquina, con los tres requisitos de arriba ya cumplidos. Déjalo fuera mientras desarrolles.
Los detalles están en Email.
Define tu primera colección
Sección titulada «Define tu primera colección»import { defineCollection, defineConfig } from '@kevolution-co/cms'
export default defineConfig({ collections: [ defineCollection({ slug: 'posts', admin: { useAsTitle: 'title' }, fields: [ { name: 'title', type: 'text', required: true }, { name: 'slug', type: 'text', required: true, unique: true, index: true }, { name: 'body', type: 'textarea' }, ], }), ],})import cloudflare from '@astrojs/cloudflare'import react from '@astrojs/react'import cms from '@kevolution-co/cms/astro'import { defineConfig } from 'astro/config'import cmsConfig from './cms.config'
export default defineConfig({ output: 'server', adapter: cloudflare(), integrations: [react(), cms(cmsConfig)],})output: 'server', el adaptador de Cloudflare y @astrojs/react son obligatorios: la integración
comprueba los tres al arrancar y falla nombrando el que falte. La referencia completa —opciones, rutas
inyectadas y qué pone en Astro.locals.cms— está en
Integración de Astro.
Genera y aplica las migraciones
Sección titulada «Genera y aplica las migraciones»Tu configuración no crea tablas por sí sola: produce migraciones SQL que revisas antes de aplicar.
Instala drizzle-kit, que es quien escribe el SQL, y añade su configuración:
pnpm add -D drizzle-kitimport { defineConfig } from 'drizzle-kit'
export default defineConfig({ dialect: 'sqlite', schema: './.cms/schema.ts', out: './migrations',})Genera:
pnpm cms db:migrate✔ Cargando config✔ Emitiendo .cms/2 colecciones desde /mi-sitio/cms.config.ts /mi-sitio/.cms/schema.ts /mi-sitio/.cms/types.d.tsSon dos colecciones y tú solo has declarado una: users la añade el CMS por su cuenta, porque es la
tabla de usuarios de la autenticación.
Lee el SQL que ha salido en migrations/ y, si estás conforme, aplícalo:
pnpm cms db:applyAplicando en local sobre el binding DB🚣 8 commands executed successfully.No hace falta que le digas cuál es la base: la saca del binding de tu wrangler.jsonc. Si el SQL no te
convence, pnpm cms db:pop borra esa migración y vuelves a empezar.
Añade .cms/ a tu .gitignore —se regenera siempre— y commitea migrations/.
La integración vuelve a generar .cms/ en cada arranque y vigila tu cms.config.ts, así que en
desarrollo no tienes que llamar a db:generate a mano. Lo que nunca es automático es generar o aplicar
una migración: eso siempre lo pides tú.
Por último, deja que TypeScript vea los tipos generados:
{ "include": [ ".astro/types.d.ts", "./.cms/types.d.ts", "**/*" ]}Hay que nombrarlo: un comodín no entra en un directorio cuyo nombre empieza por punto. Con esa línea,
Astro.locals.cms.collections.posts queda tipado con tus colecciones sin que declares nada más.
El ciclo completo, con el aviso de deriva y lo que SQLite no te deja hacer, está en Schema y migraciones.
Arranca
Sección titulada «Arranca»pnpm devYa puedes leer contenido desde una plantilla, con los tipos de tus colecciones:
---export const prerender = false
const { docs } = await Astro.locals.cms.collections.posts.find({ limit: 5 })---
<ul>{docs.map((post) => <li>{post.title}</li>)}</ul>prerender = false es imprescindible en cualquier página que lea contenido: en build no hay bindings de
Cloudflare y no hay nada que leer.
También responden /api/cms/posts —los seis endpoints de la API REST— y
/admin, que en una instalación vacía te lleva al asistente de configuración: pide los ajustes del sitio
y la primera cuenta, y con eso cierra el registro.