main content

Headless Drupal con Next.js: lo que aprendi migrando un medio digital con 2 millones de visitas mensuales

Hace tres anos, un editor de un medio digital espanol me miro con cara de pocos amigos y me dijo: "Si me quitas el boton de previsualizar, me voy". Estabamos en la segunda semana de la migracion a headless Drupal y el equipo editorial estaba al borde del motin. Habiamos prometido tiempos de carga de menos de un segundo, mejor posicionamiento en Google y una plataforma preparada para servir contenido en app movil, newsletters y cualquier canal que se inventaran en el futuro. Pero lo que ellos veian era que su flujo de trabajo de toda la vida se rompia.

Esa experiencia me enseno algo fundamental: headless Drupal no es solo un problema tecnico. Es un problema de personas, de procesos y de expectativas. Y si no lo abordas asi, acabaras con una arquitectura brillante que nadie quiere usar.

En este texto voy a contar lo que funciona, lo que no funciona y lo que habria hecho diferente despues de cuatro proyectos headless en produccion — dos medios de comunicacion, un e-commerce con catalogo de 18.000 SKUs y un portal corporativo con intranet integrada.

Por que un equipo serio se plantea desacoplar Drupal

No voy a vender la moto. Headless no es para todos ni es la solucion magica que algunos consultores presentan. Pero cuando encaja, los numeros hablan solos.

En el proyecto del medio digital que mencionaba, pasamos de un TTFB medio de 4.2 segundos (si, segundos, con picos de 7s en horas de maxima audiencia) a 0.8 segundos de manera consistente. El trafico organico crecio un 67% en seis meses, y no fue porque cambiaramos la estrategia de contenidos — publicaban exactamente lo mismo. Fue porque Google empezo a rastrear y posicionar paginas que antes tardaban una eternidad en cargar.

Las razones tecnicas que justifican el salto son concretas:

Rendimiento real, no teorico. Next.js con SSG o ISR sirve paginas pre-renderizadas desde CDN. Esto no es un "puede mejorar el rendimiento" — es pasar de servir HTML generado por PHP en cada peticion a servir archivos estaticos desde el nodo CDN mas cercano al usuario. En nuestro caso, Cloudflare con 300+ puntos de presencia globales. Un usuario en Ciudad de Mexico que accede a un medio espanol recibe la pagina en 40ms en lugar de 1.2 segundos.

Independencia real entre equipos. En el modelo monolitico, cada vez que el equipo de frontend queria probar una nueva interaccion, tenia que coordinar con el equipo de Drupal para no romper templates Twig, cachear correctamente y desplegar junto. Con headless, el equipo de React despliega cuando quiere a Vercel y el equipo de Drupal gestiona contenido sin preocuparse del renderizado. En la practica, pasamos de un deploy semanal coordinado a deploys diarios independientes.

Capacidad omnicanal genuina. La API JSON:API de Drupal sirve el mismo contenido a la web, a la app movil nativa (en aquel caso, React Native), al sistema de newsletters y al feed de Google Discover. Un solo punto de verdad para el contenido, multiples consumidores. Antes, el equipo editorial duplicaba trabajo manualmente para adaptar contenido a cada canal.

Superficie de ataque reducida. El servidor Drupal queda detras de un firewall, sin exposicion directa al usuario final. Solo el frontend (archivos estaticos en CDN) y la API (protegida con OAuth) estan expuestos. En una auditoria de seguridad post-migracion, el numero de vectores de ataque identificados bajo de 23 a 7.

Dos caminos para desacoplar (y como elegir sin arrepentirte)

Desacoplamiento completo: cuando quieres quemar las naves

Drupal deja de renderizar HTML por completo. Se convierte exclusivamente en un backend de contenido con API. Todo el frontend vive en un repositorio separado — Next.js, Gatsby, Astro, lo que prefieras. Drupal ni siquiera necesita un tema activo (bueno, si, necesita uno basico para el admin, pero nada orientado al publico).

Este fue el enfoque que usamos en el medio digital. Las ventajas son claras: maximo control sobre el frontend, rendimiento optimo, equipos totalmente independientes. Pero las implicaciones practicas son mas sutiles de lo que la documentacion oficial sugiere.

Formularios. En Drupal monolitico, crear un formulario de contacto o una encuesta es cuestion de minutos con Webform. En headless, tienes que reconstruir esa logica en React: validacion, envio, gestion de errores, proteccion anti-spam. En el proyecto del e-commerce, solo la reimplementacion de formularios (contacto, devoluciones, reclamaciones, valoraciones de producto) nos llevo tres semanas de desarrollo.

Busqueda facetada. Search API con Solr en Drupal monolitico te da busqueda facetada out of the box. En headless, necesitas exponer Solr o Elasticsearch a traves de un endpoint propio o usar un servicio externo como Algolia. Nosotros acabamos con Algolia para el medio digital (indexacion en tiempo real, velocidad brutal) y con Elasticsearch propio para el e-commerce (por volumen de consultas, Algolia se iba a 2.800 euros/mes).

Autenticacion con sesion. Las cookies de sesion de Drupal no funcionan cross-domain. Necesitas OAuth 2.0 o JWT. Esto complica funcionalidades como "recordar mi lectura", perfiles de usuario personalizados o carritos de compra persistentes.

Este camino es el correcto para: portales de contenido con gran trafico, plataformas que necesitan servir multiples canales desde una sola fuente, equipos que ya dominan React/Next.js y quieren control total.

Desacoplamiento progresivo: la transicion pragmatica

Drupal sigue renderizando la estructura principal de la pagina — cabecera, pie, layout general — pero secciones concretas son mini-aplicaciones React incrustadas en el DOM. Es como meter pequenas islas de interactividad dentro del marco tradicional de Drupal.

Lo hicimos en el portal corporativo con intranet. El sitio publico seguia siendo Drupal clasico (funcionaba bien, el trafico era moderado, no habia razon para complicarse), pero el dashboard de la intranet era una SPA en React que consumia datos de Drupal via JSON:API. Lo mejor de ambos mundos: el equipo editorial seguia con su flujo Drupal de siempre para la web publica, y el equipo de producto tenia libertad total para construir la experiencia de intranet.

Este enfoque es perfecto para: migraciones graduales donde no puedes parar y reconstruir todo, presupuestos que no permiten mantener dos infraestructuras completas desde el dia uno, equipos con experiencia mixta entre Drupal theming y desarrollo JavaScript moderno.

Un consejo que me habria ahorrado dolores de cabeza: si eliges progresivo como paso intermedio hacia completo, documenta desde el primer dia la API que expones. He visto proyectos donde el desacoplamiento progresivo se convierte en permanente porque nadie quiere reescribir los endpoints ad-hoc que se crearon "temporalmente".

Montar Drupal como backend API: configuracion que funciona en produccion

Los modulos que realmente necesitas (y los que puedes ignorar)

Despues de cuatro proyectos, mi stack de modulos headless se ha estabilizado bastante. Estos son los que instalo siempre:

JSON:API (core desde Drupal 8.7). No necesitas instalarlo, ya viene incluido. Expone todos tus tipos de contenido como endpoints REST que siguen la especificacion JSON:API. La ventaja frente al modulo RESTful clasico es que JSON:API maneja relaciones entre entidades de forma nativa — incluir la imagen de un articulo, los terminos de taxonomia y el autor en una sola peticion es trivial con el parametro include.

JSON:API Extras. Imprescindible. Te permite personalizar los nombres de los endpoints (por defecto, Drupal expone node--article, que no es muy bonito), controlar exactamente que campos se exponen (no quieres enviar campos internos al frontend) y deshabilitar recursos que no necesitas. En el e-commerce, redujimos el payload medio de la API un 40% simplemente desactivando campos que el frontend no usaba.

Simple OAuth. Gestion de autenticacion OAuth 2.0 con soporte para client credentials (comunicacion servidor a servidor, ideal para el build de Next.js) y authorization code (para usuarios que inician sesion). Configuralo con scopes separados: uno para lectura publica, otro para operaciones de escritura del CMS, otro para acciones de usuario autenticado.

Decoupled Router. Resuelve los alias de URL de Drupal a la entidad correspondiente. Cuando el frontend recibe una peticion a /noticias/economia/inflacion-mayo-2024, este modulo le dice "eso es un node de tipo article con ID 4521". Sin esto, tendrias que mantener una tabla de mapeo manual entre URLs y contenido.

Consumer Image Styles. Expone los estilos de imagen de Drupal a traves de la API. Sin este modulo, el frontend recibe la imagen original y tiene que encargarse del redimensionado. Con el, puedes pedir directamente la version thumbnail, medium o hero_banner que ya tienes configurada en Drupal. Ahorro de ancho de banda brutal — en el medio digital, el trafico de imagenes bajo un 55% tras configurar esto correctamente.

La configuracion CORS que no te dejara en ridiculo

cors.config:
  enabled: true
  allowedHeaders: ["authorization", "content-type", "accept", "origin", "x-csrf-token"]
  allowedMethods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"]
  allowedOrigins: ["https://tudominio.com", "https://preview.tudominio.com"]
  supportsCredentials: true
  maxAge: 3600

Tres errores que veo constantemente en proyectos ajenos:

  1. allowedOrigins: ["*"] con contenido privado. Si tu API sirve datos que requieren autenticacion, un wildcard en origenes significa que cualquier dominio puede hacer peticiones autenticadas. He visto intranets corporativas con esta configuracion. Una barbaridad.

  2. Olvidar el entorno de preview. Next.js tiene un modo de preview draft que necesita acceder a la API desde un dominio diferente al de produccion. Si no lo incluyes en allowedOrigins, la previsualizacion no funciona y tu equipo editorial te odiara.

  3. No incluir x-csrf-token en los headers permitidos. Necesario para operaciones de escritura desde el frontend si usas la proteccion CSRF nativa de Drupal.

El frontend con Next.js: estrategias de renderizado que marcan la diferencia

Aqui es donde la mayoria de tutoriales se quedan cortos. Dicen "usa SSG para todo" y se quedan tan anchos. La realidad es que un sitio real necesita multiples estrategias de renderizado para diferentes tipos de contenido, y elegir mal te puede costar rendimiento o funcionalidad.

Mapa de estrategias por tipo de contenido

Despues de iterar en cuatro proyectos, este es el patron que me funciona:

SSG con getStaticProps para contenido evergreen. Paginas de servicios, landing pages, paginas legales, el "quienes somos". Se generan una vez en build time y se sirven como HTML estatico desde CDN. Velocidad maxima, coste de servidor cero. En el portal corporativo, estas paginas tenian un Lighthouse Performance de 99-100 consistente.

ISR (Incremental Static Regeneration) para contenido editorial. Articulos de blog, noticias, fichas de producto. Se pre-renderizan en build, pero con un parametro revalidate que indica cada cuantos segundos Next.js debe regenerar la pagina en background. Para el medio digital usabamos revalidate: 60 (un minuto) — suficiente para que las correcciones de los editores se reflejaran rapido sin sacrificar rendimiento. Para el e-commerce, revalidate: 300 (cinco minutos) para fichas de producto, porque los precios no cambiaban con esa frecuencia.

SSR con getServerSideProps para contenido personalizado. Paginas que dependen de la sesion del usuario: dashboards, perfiles, carritos de compra, contenido restringido por suscripcion. Cada peticion genera HTML fresco en el servidor. Mas lento que SSG/ISR, pero necesario cuando el contenido es diferente para cada usuario. En el medio digital, la seccion "Mi perfil" y las recomendaciones personalizadas usaban SSR.

Client-side rendering para interacciones que no necesitan SEO. Comentarios, filtros en tiempo real, buscadores internos, notificaciones. Se renderizan en el navegador del usuario despues de la carga inicial. No los ve Google (bueno, a veces si, pero no de manera fiable), asi que no los uses para contenido que necesites indexar.

La libreria next-drupal: lo que resuelve y lo que no

next-drupal de Chapter Three es la libreria de referencia para conectar Next.js con Drupal. Abstrae bastante bien la resolucion de rutas, la autenticacion OAuth y la generacion de paths para SSG. Un ejemplo real de como la usamos:

const node = await drupal.getResourceByPath<DrupalNode>(
  `/${params.slug.join("/")}`,
  {
    params: {
      "fields[node--article]": "title,body,field_image,field_author,field_category,created",
      include: "field_image,field_author,field_category",
    },
  }
);

Ese fields es clave. Sin el, Drupal te devuelve todos los campos del tipo de contenido, incluidos los que no necesitas. En el e-commerce, una ficha de producto sin filtrar campos pesaba 48KB de JSON. Con campos filtrados, 6KB. Multiplicado por 18.000 productos, la diferencia en tiempo de build era de 45 minutos frente a 12 minutos.

Lo que next-drupal no resuelve bien (al menos en la version 1.x que usamos): previsualizacion de contenido no publicado con roles granulares, busqueda avanzada, y formularios complejos. Para eso necesitas codigo custom.

Invalidacion de cache: el punto donde la mayoria de proyectos headless patinan

Te voy a contar algo que nos paso en produccion. Tres semanas despues del lanzamiento del medio digital, un periodista publico una noticia con un error factual grave. Corrigio el articulo en Drupal en menos de dos minutos. Pero la version con el error siguio visible en la web durante una hora porque la invalidacion de cache no estaba configurada correctamente. El director del medio nos llamo bastante enfadado, con razon.

Desde entonces, la invalidacion de cache es lo primero que configuro y lo ultimo que doy por terminado. Dos estrategias que funcionan:

Revalidacion on-demand via webhook. Drupal dispara un webhook (usando el modulo Webhooks o un hook_entity_update custom) cada vez que se publica, actualiza o despublica un contenido. El webhook llama a un endpoint de Next.js (/api/revalidate) que regenera especificamente esa pagina. El tiempo entre "guardar en Drupal" y "ver el cambio en la web" baja a 2-5 segundos. Asi lo configuramos en el medio digital despues del incidente, y nunca volvimos a tener ese problema.

// /api/revalidate.ts
export default async function handler(req, res) {
  const secret = req.headers["x-revalidation-secret"];
  if (secret !== process.env.REVALIDATION_SECRET) {
    return res.status(401).json({ message: "Invalid secret" });
  }
  
  const { path } = req.body;
  await res.revalidate(path);
  return res.json({ revalidated: true, path });
}

Purge + CDN para invalidacion a nivel de CDN. El modulo Purge de Drupal integrado con Cloudflare o Fastly envia ordenes de invalidacion directamente al CDN cuando el contenido cambia. Util cuando tienes multiples capas de cache. En el e-commerce, usabamos Fastly con cache tags: cada pagina de producto tenia un tag unico, y al actualizar el producto se invalidaba solo ese tag. Invalidacion quirurgica en lugar de purgar todo el cache.

Mi recomendacion: usa ambas estrategias juntas. El webhook para la regeneracion de Next.js y Purge+CDN para las capas intermedias. Cinturon y tirantes. El coste de implementacion es bajo y te ahorra incidentes en produccion.

SEO en arquitectura headless: lo que cambia y lo que se mantiene

He oido a gente decir que headless es malo para SEO. Es mentira, pero entiendo de donde viene la confusion. Si tu frontend es una SPA pura que renderiza todo en el cliente, Google va a tener problemas para indexarte. Pero si usas Next.js con SSG o SSR, el HTML que recibe el crawler es identico al que recibe un usuario — pre-renderizado, completo, con todo el contenido visible.

Canonical URLs. En Drupal monolitico, el modulo Metatag se encarga. En headless, tienes que definirlas en el frontend. Con Next.js 13+ y el App Router, usas la API de Metadata:

export async function generateMetadata({ params }): Promise<Metadata> {
  const node = await getArticle(params.slug);
  return {
    title: node.title,
    description: node.field_meta_description,
    alternates: {
      canonical: `https://tudominio.com/${params.slug}`,
    },
  };
}

Sitemap dinamico. next-sitemap consulta la API de Drupal para generar el sitemap automaticamente. Lo configuramos para que se regenere cada noche con un cron en Vercel. En el medio digital, con 40.000+ articulos, el sitemap se generaba en 3 minutos y se partia automaticamente en chunks de 50.000 URLs (limite del protocolo).

Datos estructurados (schema.org). Todo el markup de schema.org que antes vivia en templates Twig tiene que migrar al frontend. Es trabajo tedioso pero necesario. En el medio digital, implementamos Article, NewsArticle, BreadcrumbList y Organization. En el e-commerce, Product, Offer, AggregateRating y FAQPage. El impacto en rich snippets fue inmediato — las fichas de producto empezaron a mostrar estrellas y precios en los resultados de Google a las dos semanas de la migracion.

Cuando headless Drupal NO es la respuesta (y esta bien admitirlo)

Seria deshonesto pintar headless como la solucion universal. He rechazado proyectos donde los clientes querian headless "porque esta de moda" pero su contexto no lo justificaba. Estas son las situaciones donde digo que no:

Sitios pequenos con trafico moderado. Si tu web corporativa tiene 5.000 visitas al mes y 20 paginas, el rendimiento de Drupal monolitico con un buen hosting y BigPipe activado es mas que suficiente. El TTFB estara en 200-400ms, que es perfectamente aceptable. El coste de mantener dos stacks (Drupal + Next.js) no se justifica.

Equipos sin experiencia en JavaScript moderno. Si tu equipo sabe Drupal y PHP pero nunca ha tocado React, la curva de aprendizaje va a retrasar el proyecto meses. Lo he visto: una agencia intento hacer headless con un equipo de Drupal puro, y el proyecto que deberia haber durado 4 meses se extendio a 11. Es mejor invertir en formar al equipo primero o contratar perfiles frontend dedicados antes de dar el salto.

Dependencia fuerte de Layout Builder o Paragraphs con renderizado complejo. Si tus editores construyen paginas arrastrando componentes en Layout Builder con previsualizacion en tiempo real, replicar esa experiencia en un frontend desacoplado es extremadamente costoso. Existen soluciones (Storybook + preview mode), pero ninguna llega al nivel de usabilidad del Layout Builder nativo. Si tu equipo editorial depende de esa herramienta, piensalo dos veces.

Presupuestos ajustados sin margen para imprevistos. Un proyecto headless siempre tiene mas imprevistos que uno monolitico. Formularios que hay que reimplementar, flujos de autenticacion que no funcionan como esperabas, integraciones con terceros que asumian cookies de sesion. Si tu presupuesto ya esta justo para la funcionalidad basica, no anadiras la complejidad extra.

En todos estos casos, la recomendacion honesta es: optimiza el monolito. BigPipe, cache de vistas precompilado, CDN con Varnish, Redis para cache de entidades. Puedes conseguir tiempos de carga de 400-600ms con esta configuracion, que para muchos proyectos es mas que suficiente.

Numeros reales: comparativa entre monolitico y headless en produccion

Esta tabla no viene de la documentacion — son datos reales de los cuatro proyectos en los que he trabajado, promediados:

Aspecto Drupal monolitico optimizado Headless Drupal + Next.js
TTFB medio 380ms - 1.100ms 25ms - 80ms (CDN)
Lighthouse Performance 55 - 75 92 - 100
Tiempo de build inicial N/A 8 - 45 min (segun volumen)
Autonomia de equipos Acoplada, deploys coordinados Alta, deploys independientes
Complejidad operativa Media Alta (dos infras, CI/CD doble)
Edicion en contexto Nativa, inmediata Requiere preview mode custom
Coste de hosting mensual 80 - 200 EUR 120 - 350 EUR (Drupal + Vercel/CDN)
Tiempo de desarrollo inicial 1x 1.4x - 1.8x
Velocidad de iteracion post-lanzamiento Moderada Alta (frontend independiente)

El coste extra de desarrollo y hosting se amortiza cuando el trafico justifica la diferencia de rendimiento. En el medio digital, el aumento del 67% en trafico organico genero ingresos publicitarios adicionales que cubrieron el sobrecoste en menos de cuatro meses.

Lo que le diria al equipo editorial que tenia miedo del cambio

Vuelvo a la historia del principio. El editor que amenazaba con irse si le quitaban la previsualizacion. Al final no se fue. Tres cosas lo convencieron:

Primera, le montamos un preview mode decente. No era tan inmediato como el preview de Drupal monolitico (habia un retardo de 1-2 segundos), pero veia exactamente como quedaria el articulo en la web real, con la tipografia real, el layout real y las imagenes en su tamano final. Eso no lo tenia antes — en el monolito, la previsualizacion usaba el tema de admin, que era bastante diferente del tema publico.

Segunda, el tiempo de publicacion bajo drasticamente. Antes, publicar un articulo urgente y que fuera visible para los lectores podia tardar 30-60 segundos por la cache de Varnish y la regeneracion de paginas. Con headless y revalidacion on-demand, en 3 segundos el articulo estaba en la web. Para un medio de noticias, eso es la diferencia entre llegar primero o llegar segundo.

Tercera, y esta fue la que realmente les gano, el analytics mejoro. Paginas que cargaban en menos de un segundo tenian tasas de rebote un 23% mas bajas y tiempo de sesion un 31% mas alto. Mas lectores que se quedaban significaba mas paginas vistas, mas impresiones publicitarias y mejores metricas para negociar con anunciantes. Cuando el director comercial vio esos numeros, se convirtio en el mayor defensor del proyecto.

El camino que sigue despues del desacoplamiento

Si algo me han ensenado estos proyectos, es que headless Drupal no es un destino — es un punto de partida. Una vez que tienes tu contenido expuesto via API bien disenada, las posibilidades se multiplican. El medio digital anadio una app movil en tres meses reutilizando el 90% de los endpoints. El e-commerce integro un chatbot que consultaba fichas de producto via la misma API. El portal corporativo conecto su intranet con Microsoft Teams para mostrar noticias internas directamente en el canal de la empresa.

La clave esta en tratar la API no como un puente temporal entre Drupal y Next.js, sino como el producto principal. Documentala con OpenAPI, versionala, monitoriza su rendimiento, trata los breaking changes con el mismo rigor que tratarias un cambio en una API publica. Tu yo del futuro te lo agradecera.

Si tu equipo esta evaluando si el desacoplamiento tiene sentido para vuestro proyecto, o si ya habeis empezado y os habeis encontrado con alguno de los problemas que describo aqui, hablemos sin compromiso para revisar vuestra arquitectura y trazar un plan realista que tenga en cuenta tanto la parte tecnica como el impacto en vuestro equipo editorial.