Un CMS Headless Sin Build Step: PHP + Cockpit, Renderizado en el Servidor
Se puede usar un CMS headless sin build step de JS. Así combinamos Cockpit v2 con PHP renderizado en el servidor, deploys por FTP y cero pipeline de npm.
Se puede usar perfectamente un CMS headless sin build step de JavaScript. Se combina una API de contenidos como Cockpit v2 con PHP renderizado en el servidor, y la experiencia de edición se mantiene desacoplada y moderna mientras el front end se entrega como HTML puro — sin bundler, sin node_modules en el servidor, sin rebuild en cada cambio de contenido. Esta es la stack que sirve el sitio que estás leyendo, y a continuación está exactamente cómo está montada, dónde gana y dónde sería la elección equivocada.
Qué significa de verdad "CMS headless sin build step"
Un CMS headless almacena contenido y lo expone a través de una API, sin opinión sobre cómo lo renderizas. Cockpit CMS v2 es un CMS headless self-hosted en PHP: defines colecciones (por ejemplo "journal", "portfolio", "team") como modelos JSON, editas entradas en su UI de administración y las lees de vuelta por una API REST. La "cabeza" — lo que convierte el contenido en páginas — es enteramente tuya.
"Sin build step" es la parte en la que la gente se equivoca. No significa ninguna herramienta. Significa ninguna compilación entre tu código fuente y lo que ejecuta el servidor. No hay bundler transformando .jsx o .ts en artefactos a desplegar, no hay framework que exija npm run build antes de que exista una sola página, no hay directorio dist/ que sea la verdadera fuente de verdad. Nuestros archivos PHP son lo que se despliega. El navegador recibe HTML renderizado en el servidor en el primer byte.
En concreto, el front end aquí es PHP 8.3 en alojamiento compartido de OVH, detrás de Cloudflare. Una petición llega a router.php, que despacha a una página como journal-post.php, que llama a la API de Cockpit, recibe JSON y devuelve HTML. Esa es toda la pipeline. No hay React, no hay hidratación, no hay router del lado del cliente.
La versión corta
- Edición: administración de Cockpit v2 (PHP self-hosted, base de datos SQLite).
- Renderizado: plantillas PHP del lado del servidor que obtienen JSON de la API REST de Cockpit en cada petición, con una caché corta de edge/disco.
- Build step: ninguno para contenido. El único "build" es minificar CSS y JS — y hasta eso es opcional y se ejecuta en local, no en el servidor.
- Deploy:
./deploy.sh— minificar, espejar por FTP a OVH, purgar Cloudflare. Sin runner de CI. - i18n: traducciones por campo guardadas en Cockpit (
title,title_pt,title_es), servidas mediante prefijos de URL (/pt/,/es/).
Por qué un CMS headless en PHP en lugar de un SSG
La respuesta por defecto de los años 2020 para "front end de CMS headless" es un generador de sitios estáticos — Next.js, Astro, Gatsby — que extrae de la API en tiempo de build y emite archivos estáticos. Es una arquitectura genuinamente buena. Recurrimos a ella en muchos proyectos. Pero acarrea un impuesto que ningún tutorial menciona.
Un SSG acopla la publicación a un build. Corriges una errata en el CMS y la página no cambia hasta que se ejecuta un build y se vuelve a desplegar. Para ocultar esa latencia montas webhooks, regeneración estática incremental (ISR), una cola de builds, una plataforma de alojamiento que ejecuta los builds, y toda una historia de invalidación de caché para cuando el ISR queda obsoleto. Nada de esto es difícil de forma aislada. Todo esto son piezas móviles que pueden romperse a las 23h de un viernes, y la mayoría existe para disimular el hecho de que convertiste una edición de contenido en un despliegue de software.
El PHP renderizado en el servidor colapsa todo eso. El contenido es datos, obtenidos en tiempo de petición. Un editor guarda en Cockpit, el cambio está en línea en la siguiente petición (o en la siguiente expiración de caché — segundos, no un build). No hay webhook, no hay minuto de build, no hay tarea de regeneración. El "deploy" solo ocurre cuando cambia el código, lo que en un sitio de marketing o un journal de estudio es drásticamente menos frecuente que el cambio de contenido.
Esta es la tesis central: para sitios impulsados por contenido, donde los editores publican más a menudo de lo que los desarrolladores despliegan, desacoplar el contenido del build vale más que el rendimiento bruto de archivos estáticos prerenderizados — sobre todo cuando, de todos modos, hay un CDN de edge haciendo el trabajo pesado de caché.
La historia del deploy: FTP, minificar, purgar
Como no hay artefacto de build, el deploy es casi insultantemente simple. Todo es un único script:
./deploy.sh # minificar → espejar por FTP → purgar Cloudflare
Tres pasos:
- Minificar. Regeneramos
assets/css/main.min.csscon rcssmin yassets/js/*.min.jscon terser — pero solo si la fuente es más reciente que la salida. Esta es la única "compilación" del sistema, es opcional y se ejecuta en la máquina del desarrollador. El servidor nunca ve una toolchain. - Espejo por FTP.
lftpespeja el directorio de trabajo a/www/en OVH, solo aditivo (sin--delete), excluyendostorage/, la instalación decockpit/, secretos,.git/ynode_modules. Los archivos PHP que escribiste son los archivos PHP que se ejecutan. No hay transpilación de por medio que depurar. - Purga de Cloudflare. Una llamada a la API
purge_everythinglimpia la caché de edge para que el nuevo código esté en línea globalmente en segundos.
Ejecutamos ./deploy.sh --dry-run primero siempre que cambia la lista de exclusiones, para que lftp imprima lo que subiría antes de mover nada. Sin plataforma de CI/CD, sin minutos de build, sin pipeline en YAML. Este script es el CI/CD. Un junior puede leerlo de arriba abajo en dos minutos y entender todo el camino del portátil a producción — lo que, en sí mismo, es una característica de fiabilidad.
SQLite como base de datos del CMS: los trade-offs honestos
Cockpit v2 usa por defecto SQLite, un único archivo en disco, y lo mantenemos así. Esto sorprende a la gente, así que seamos precisos sobre cuándo es lo correcto y cuándo no.
Dónde gana SQLite para un CMS headless:
- No hay servidor de base de datos que operar. Sin pools de conexiones, sin proceso separado que monitorizar, sin credenciales que rotar, sin factura de Postgres gestionado. La base de datos es un archivo del que haces backup copiándolo.
- Es rápido en lecturas. Un journal de estudio o un sitio de marketing es abrumadoramente tráfico de lectura sobre un conjunto de datos medido en megabytes. Leer del disco local con SQLite es más rápido que un round-trip de red a un servidor de base de datos.
- Los backups son triviales. Snapshot del archivo. Restauración reemplazándolo. Versiónalo si quieres.
Dónde SQLite es la herramienta equivocada — seamos honestos:
- Concurrencia de escritura. SQLite serializa las escrituras con un lock a nivel de base de datos. Para un puñado de editores esto es irrelevante. Para una app con muchos usuarios escribiendo a la vez, es un muro — recurre a Postgres o MySQL (Cockpit los soporta).
- Escalabilidad horizontal. Un archivo en el disco de un servidor no escala a una flota de servidores de aplicación compartiendo una base de datos. Si necesitas varios nodos de escritura, SQLite se acabó.
- Vive en el mismo host. En alojamiento compartido, la disponibilidad de tu CMS está atada a ese host. Lo mitigamos con Cloudflare por delante y backups disciplinados, pero es un acoplamiento real.
La regla que usamos: si el contenido lo edita un equipo pequeño y lo lee el mundo, SQLite no es un compromiso — es la elección correcta, aburrida y duradera. Si la base de datos es la aplicación (contenido generado por usuarios, mucho fan-out de escritura, escala multi-tenant), no lo es.
i18n por campo, no por build
Este sitio es trilingüe — inglés, portugués (pt-PT) y español — y el modelo de i18n es donde el enfoque sin build realmente compensa. La traducción vive por campo, dentro del CMS. Cockpit guarda las variantes localizadas junto al campo base: una entrada de journal tiene title, title_pt, title_es y lo mismo para excerpt y body. Un editor traduce un post en la misma pantalla de administración, sin código de por medio.
El front end resuelve el idioma a partir del prefijo de la URL. /pt/journal/... sirve portugués, /es/... sirve español, las rutas sin prefijo sirven inglés. La plantilla PHP elige el campo correcto en tiempo de renderizado — a grandes rasgos:
$lang = current_locale(); // 'en' | 'pt' | 'es'
$title = $entry['title_' . $lang] ?? $entry['title'];
La recompensa: añadir una traducción nunca dispara un rebuild. Guardas el campo en Cockpit, la página localizada está en línea en la siguiente petición. Con un SSG, tres idiomas pueden significar el triple de superficie de build y una matriz de rutas por idioma regenerada en cada cambio. Aquí es una búsqueda de campo extra en tiempo de petición. (Aprendimos por las malas a no redirigir automáticamente a los visitantes a /pt/ o /es/ según navigator.language — eso secuestra la URL que la gente realmente pidió. El idioma es una elección, no una conjetura.)
Cuándo esta stack gana a Next.js / Astro — y cuándo no
Elige PHP renderizado en el servidor + CMS headless cuando:
- El contenido cambia mucho más a menudo que el código, y los editores no deberían esperar a un build para publicar.
- El sitio tiene forma de contenido-y-marketing: páginas, posts, portfolio, colecciones estructuradas — no una web app con estado.
- Quieres un deploy que una sola persona entiende por completo, sin lock-in de plataforma y sin factura de minutos de build.
- Estás en alojamiento compartido barato con un CDN de edge, y prefieres no operar un servidor de base de datos ni un runner de build.
- La mantenibilidad a largo plazo importa más que perseguir el framework del año. El PHP de 2024 funcionará en 2034.
Recurre mejor a Next.js o Astro cuando:
- Estás construyendo una aplicación — autenticación, dashboards, tiempo real, mucho estado en el cliente. PHP renderizado en el servidor sin framework de JS es la herramienta equivocada para interactividad rica.
- El equipo ya vive en React/TypeScript y el modelo de componentes, la seguridad de tipos y el ecosistema son ganancias netas de productividad para ese equipo.
- Realmente necesitas HTML estático prerenderizado en el edge absoluto, con cero dependencia del origen — un sitio de documentación, una página de lanzamiento que espera un pico de tráfico donde no toleras ningún origen en el camino.
- Quieres la DX de una capa de contenido tipada, pipelines de optimización de imagen y MDX ya incluidos, y estás contento de asumir la maquinaria de build/deploy que viene con ello.
No hay ganador universal. El error es tratar "CMS headless" como sinónimo de "SSG de JavaScript." Headless solo significa que el contenido está desacoplado del renderizado. PHP renderizando ese contenido en cada petición es tan "headless" como un build de Next.js consumiendo la misma API — simplemente mueve el trabajo de tiempo de build a tiempo de petición, y en un sitio de contenido detrás de un CDN esa es a menudo la mejor decisión.
Los límites, dichos sin rodeos
No fingimos que esto carezca de aristas:
- Sin seguridad de tipos en la frontera de la API. Un campo de Cockpit renombrado no dará error en tiempo de compilación — renderizará vacío. Lo cazamos con fallbacks de null-coalescing y un smoke check tras el deploy, no con un type checker.
- Renderizar en cada petición necesita caché. Golpear la API del CMS en cada petición sin caché sería un desperdicio; Cloudflare y una caché corta en el origen hacen el trabajo que un SSG hace en tiempo de build. Estás cambiando una caché de build por una caché de edge.
- SQLite ata la disponibilidad de escritura del CMS a un host. Bien para un equipo editorial pequeño; mal para un producto multiusuario con mucha escritura.
- El renderizado es tuyo. Ningún framework te da routing, i18n o manejo de imágenes gratis — los escribimos nosotros (unos cientos de líneas de PHP deliberado). Eso es una característica si valoras la transparencia, un coste si querías todo incluido.
También construimos la versión con framework de JavaScript — es la respuesta correcta para mucho trabajo de cliente. Pero para un sitio impulsado por contenido, mantenido por un equipo pequeño, un CMS headless sin build step es más rápido de poner en línea, más barato de mantener y drásticamente más sereno de operar. El mejor build step es, muchas veces, el que no añadiste.