8 MIN LECTURA · Pedro Thomaz

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.

Un CMS Headless Sin Build Step: PHP + Cockpit, Renderizado en el Servidor

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

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:

  1. Minificar. Regeneramos assets/css/main.min.css con rcssmin y assets/js/*.min.js con 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.
  2. Espejo por FTP. lftp espeja el directorio de trabajo a /www/ en OVH, solo aditivo (sin --delete), excluyendo storage/, la instalación de cockpit/, secretos, .git/ y node_modules. Los archivos PHP que escribiste son los archivos PHP que se ejecutan. No hay transpilación de por medio que depurar.
  3. Purga de Cloudflare. Una llamada a la API purge_everything limpia 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:

Dónde SQLite es la herramienta equivocada — seamos honestos:

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:

Recurre mejor a Next.js o Astro cuando:

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:

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.