Patrones SEO para WordPress headless: las siete cosas que rompen la mayoría de las migraciones
WordPress headless se vende por los Core Web Vitals, la reutilización de contenido y la rapidez editorial. Si nadie está atento, por el camino entierra siete señales SEO concretas. Hemos hecho suficientes migraciones de este tipo para saber cuáles cuestan semanas de recuperación y cuáles se mantienen bien de forma mecánica.
Este artículo es la lista de verificación. No sustituye al pilar del servicio de WordPress headless, que expone el argumento de arquitectura. Es lo que revisamos antes, durante y después de cada migración, en ese orden.
En resumen
- Conserva las URL canónicas a nivel de URL completa, no solo del slug.
- Conserva el hreflang en el HTML, no solo en el sitemap.
- Renderiza las metaetiquetas y el JSON-LD en el servidor, no en el cliente.
- Migra el historial de redirecciones antes de cambiar las URL, no después.
- Mantén un único sitemap como fuente de verdad, no dos.
- Oculta el origen de WordPress del índice de los buscadores.
- Traslada el texto alternativo de las imágenes y sus datos estructurados.
Cómo conservar las URL canónicas en una migración headless
Una URL canónica es una promesa. Cada enlace externo, cada entrada en el índice de Google y cada vez que alguien comparte la página en redes sociales cuentan con ella. Una migración headless que recorta en silencio un segmento de la ruta, cambia mayúsculas y minúsculas o reordena los parámetros de consulta acaba de romper todas esas promesas sin que nadie se dé cuenta.
Dos reglas. Primero, recopila el conjunto completo de URL canónicas de la instalación antigua de WordPress antes de tocar el front-end. Exportamos cada entrada, página y página de taxonomía publicadas con la URL que Google tiene indexada; esa es la fuente de verdad. Segundo, escribe la canónica en la respuesta HTML del front-end headless, no en una modificación del <head> hecha en el cliente. Los motores generativos y los motores de respuesta analizan el HTML inicial; para ellos, las metaetiquetas modificadas en el cliente no existen.
Si tienes que cambiar una URL, redirige con un 301 de la antigua a la nueva y mantenlo al menos un año.
Etiquetas hreflang en el HTML de WordPress headless
Los sitios WordPress multilingües gestionan las traducciones con WPML, Polylang o una solución propia. El mapeo termina siendo correcto en la base de datos. Después, el front-end headless tiene que renderizar <link rel="alternate" hreflang="..."> para cada variante de idioma en la respuesta HTML.
El patrón que se le escapa a la mayoría de las agencias: el hreflang tiene que ser autorreferencial. La página en inglés se incluye a sí misma y a todas las alternativas traducidas. La página en polaco se incluye a sí misma y a todas las alternativas. Las dos listas coinciden. La guía de Google sobre versiones localizadas exige que cada versión se incluya a sí misma y a todas las demás, e ignora las etiquetas cuando dos páginas no se enlazan mutuamente.
Tratamos la generación del hreflang como parte del build, no como una decisión en tiempo de ejecución. El mapa de rutas se calcula en el build y se convierte en hash, y cualquier desviación hace fallar el build.
Renderizado en el servidor de metaetiquetas y JSON-LD
La regresión SEO más habitual que hemos visto en migraciones headless: metaetiquetas y JSON-LD insertados con JavaScript después de cargar la página. El navegador los ve. Googlebot a veces los ve. Los motores generativos, los asistentes de voz y la mayoría de los rastreadores de LLM normalmente no.
Dos reglas. Renderiza las metaetiquetas, la canónica, el Open Graph y cada bloque JSON-LD de Schema.org en la respuesta HTML inicial. Con Astro, es el comportamiento predeterminado. Con Next.js, significa renderizar en el servidor (metadatos del App Router, o la ruta heredada con getServerSideProps) y no depender de que next/head se vuelva a ejecutar en el cliente.
Lo mismo vale para las imágenes: un elemento <img> con alt y src en el HTML es indexable. Un <img> inyectado después de un efecto en el cliente es invisible para la mayoría de los rastreadores y para los pipelines de datos de entrenamiento de IA.
Reutilizar el JSON-LD de Yoast en WordPress headless
WordPress con Yoast SEO o Rank Math ya genera un buen JSON-LD de Article, Product y Organization. En una migración headless, la tentación es reescribirlo desde cero en el front-end. No caigas en ella.
Lee el JSON-LD existente desde el origen de WordPress mediante el endpoint REST o GraphQL. Pásalo tal cual. Añade solo lo que el front-end sabe legítimamente y WordPress no (por ejemplo, marcas de tiempo del build para dateModified si tu flujo editorial no toca las fechas). Dos sistemas generando JSON-LD solapado es la forma en que los informes de resultados enriquecidos de Search Console empiezan a fallar.
En nuestras propias páginas usamos los componentes de la fase 0 en src/components/seo/: DirectAnswer, FAQ y Quote. Cada uno emite su propio JSON-LD mínimo sin solaparse con el schema Article de la página.
Cómo migrar las redirecciones antes de cambiar las URL
El orden importa. La lista de verificación previa a la migración recoge todas las redirecciones internas y externas, incluidas las silenciosas (/wp-content/... a /uploads/..., redirecciones por código de país, variantes AMP). El nuevo front-end publica esas redirecciones el día cero, antes del cambio público a las nuevas URL.
En Cloudflare Pages mantenemos el archivo _redirects por debajo del límite de la plataforma de 2000 reglas. Un build que superaría el límite falla. Todo lo que necesite más de 2000 reglas va a un Worker con lógica de redirecciones con parámetros.
Cuando el DNS público por fin apunta al nuevo front-end, ninguna redirección es nueva en producción: cada regla se ha probado en el build durante semanas antes del cambio.
Un único sitemap XML para WordPress headless
WordPress 5.5 añadió un /wp-sitemap.xml predeterminado. Yoast SEO y Rank Math añaden los suyos. El framework del front-end headless también genera un sitemap. Tres sitemaps en el mismo dominio son la receta perfecta para volver loca a Search Console.
La regla: elige un sitemap canónico y desactiva o redirige los demás. Normalmente generamos el sitemap desde el framework del front-end, para que las URL coincidan exactamente con el sitio público, y redirigimos con 301 el sitemap del origen de WordPress al del front-end. Así el origen de WordPress se vuelve invisible para la búsqueda.
Cómo aplicar noindex al backend de WordPress headless
Una migración headless deja el origen de WordPress en funcionamiento, normalmente en un subdominio o en un nombre de host privado. Sigue sirviendo HTML renderizado, tiene un sitemap operativo y responde a consultas REST. Los buscadores que encuentren ese origen lo indexarán como un duplicado del sitio público, y el duplicado no será el que se posicione.
Tres controles. El robots.txt del origen bloquea todas las rutas excepto los endpoints REST y GraphQL. El origen envía X-Robots-Tag: noindex, nofollow en las cabeceras HTTP de cada respuesta HTML. El sitemap del origen se elimina o devuelve un 410.
Si tu origen está en el mismo dominio que el sitio público, bajo un prefijo de ruta (por ejemplo, /wp-admin/ o /wp/), se aplican los mismos controles, limitados a esas rutas.
Cómo procesa Google el JavaScript de un front-end headless
Según la guía de conceptos básicos de SEO para JavaScript de Google Search Central, Google procesa las aplicaciones web con JavaScript en tres fases: rastreo, renderizado e indexación. Googlebot pone en la cola de renderizado todas las páginas que responden con un código 200, salvo que una metaetiqueta o una cabecera robots indique que no se indexen. La página puede pasar unos segundos en esa cola, pero la documentación advierte que puede tardar más.
Para un front-end headless la consecuencia es práctica. Lo que llega en la respuesta HTML inicial se procesa en la primera pasada; lo que depende de JavaScript espera al renderizado. La misma guía afirma que el renderizado en el servidor o el prerenderizado sigue siendo una buena idea, porque hace el sitio más rápido para usuarios y rastreadores y porque no todos los bots pueden ejecutar JavaScript. Por eso tratamos el HTML inicial como el contrato SEO de cada ruta: título, meta descripción, canonical, hreflang, enlaces y JSON-LD tienen que estar ahí sin ejecutar nada.
Las imágenes siguen la misma lógica. La guía de Google sobre carga diferida cita la carga diferida integrada en el navegador y la API IntersectionObserver como métodos compatibles con la búsqueda, porque cargan el contenido cuando es visible en el viewport y no dependen de desplazarse ni de hacer clic, cosa que Google Search no hace. También desaconseja aplicar carga diferida a las imágenes que el usuario ve de inmediato. La comprobación está en el HTML renderizado: la URL de la imagen tiene que estar en el atributo src del elemento <img>, algo que muestra la herramienta de inspección de URLs de Search Console.
Por qué el renderizado dinámico no resuelve el SEO de WordPress headless
Durante años, la salida rápida para una SPA conectada a WordPress fue el renderizado dinámico: servir HTML prerenderizado a los bots y la aplicación JavaScript a los usuarios. Google Search Central lo describe hoy como una solución provisional y no recomendada, y en su lugar recomienda el renderizado en el servidor, el renderizado estático o la hidratación.
La misma página aclara que Googlebot no suele considerar cloaking el renderizado dinámico, pero que servir contenido completamente distinto a usuarios y rastreadores sí puede considerarse cloaking. En un proyecto headless eso obliga a mantener dos caminos de renderizado que deben producir el mismo contenido, y nadie se entera cuando divergen. Un front-end que renderiza en el servidor o en el build elimina esa duplicación: usuarios, Googlebot y rastreadores sin JavaScript reciben el mismo HTML.
Códigos de estado HTTP y soft 404 en un front-end headless
Cuando el front-end consulta la REST API de WordPress por un slug que no existe, recibe una colección vacía, no un error. Si con eso renderiza una plantilla de “no encontrado” con código 200, Googlebot la mete en la cola de renderizado como a cualquier página válida y el resultado es un soft 404. Con renderizado en el servidor, la corrección es directa: si la respuesta está vacía, el front-end devuelve un 404 real, o un 410 si el contenido se retiró a propósito.
Lo mismo pasa con borradores y entradas privadas. Según la referencia del endpoint de entradas, el parámetro status de la colección vale publish por defecto, así que una entrada despublicada desaparece de la respuesta y su ruta tiene que responder con un código de error, no con una página vacía.
Para rutas que solo se resuelven en el cliente, la guía de Google propone dos estrategias: una redirección con JavaScript a una URL en la que el servidor responde con 404 (por ejemplo /not-found), o añadir con JavaScript <meta name="robots" content="noindex"> a las páginas de error.
Canonical, noindex y enlaces generados con JavaScript
La misma guía de Google fija tres reglas que en headless se rompen con facilidad.
- Canonical. Si la canonical se define con JavaScript, debe tener siempre el mismo valor que en el HTML original; varias etiquetas
rel="canonical"o etiquetas contradictorias pueden dar resultados inesperados. En headless ocurre cuando el HTML del servidor trae la canonical del origen de WordPress y un componente de cliente la reescribe con la URL del front-end. - Noindex. Cuando Google encuentra
noindex, puede saltarse el renderizado y la ejecución de JavaScript. Un front-end que envíanoindexen el HTML inicial mientras carga los datos y lo retira después con JavaScript deja la página fuera del índice. Si quieres la página indexada, el código original no debe llevarnoindex. - Enlaces. Google solo descubre enlaces que son elementos
<a>con atributohref, y la guía pide no usar fragmentos para cargar contenido distinto, sino la History API. Un menú que navega con manejadoresonClicko con rutas del tipo#/entradadeja sin rastrear la estructura interna que WordPress tenía.
Cómo sacar el inventario de URL de la REST API de WordPress
La primera regla de este artículo pide exportar todas las URL publicadas antes de tocar el front-end. La REST API lo permite sin plugins. Según el manual de paginación de la REST API, los endpoints de colección devuelven un número limitado de elementos por petición; per_page acepta un entero de 1 a 100 y page indica qué página de resultados devolver. Cada respuesta incluye las cabeceras X-WP-Total, con el número total de registros, y X-WP-TotalPages, con el número total de páginas. Basta leer X-WP-TotalPages en la primera petición a /wp-json/wp/v2/posts?per_page=100 y recorrer las páginas restantes.
De cada entrada interesan dos campos de la referencia de entradas: link, la URL de la entrada tal como la publica WordPress, y modified_gmt, la fecha de la última modificación en GMT. El primero es la lista con la que se compara cada ruta del nuevo front-end; el segundo alimenta lastmod en el sitemap. El argumento modified_after limita la respuesta a entradas modificadas después de una fecha ISO 8601, lo que permite builds incrementales sin descargar todo el contenido cada vez. El mismo patrón de paginación sirve para /wp/v2/pages y para los endpoints de taxonomías.
Resolución de URL, vistas previas y caché con WPGraphQL
WPGraphQL es un plugin libre y de código abierto que añade a WordPress un esquema y una API GraphQL ampliables. Su documentación muestra cómo consultar una entrada usando su URI, es decir, la ruta de la entrada, como identificador. Para el SEO esto importa porque la ruta pública y el contenido salen de la misma fuente: el front-end pide el nodo por la ruta que recibe y no necesita una tabla propia que traduzca rutas a identificadores.
Las vistas previas exigen más cuidado. Según la documentación de vistas previas, WPGraphQL puede devolver la versión no publicada de una entrada, pero la petición tiene que estar autenticada con un método que WordPress reconozca. Si no lo está, WPGraphQL devuelve el nodo publicado como si no se hubiera pedido una vista previa, sin error. Una ruta de vista previa con la autenticación rota muestra en silencio el contenido publicado. Por eso las rutas de vista previa llevan noindex, quedan fuera del sitemap y no pasan por la caché pública, y el campo isPreview sirve para comprobar que la vista previa se aplicó de verdad.
La caché decide cuánto tardan en llegar al HTML los cambios de título, descripción o canonical hechos en WordPress. La documentación de rendimiento indica que WPGraphQL admite peticiones GET, que pueden almacenarse en la caché de una CDN o de un proxy, y que la extensión WPGraphQL Smart Cache etiqueta las respuestas con la cabecera X-GraphQL-Keys y emite un evento con las etiquetas afectadas cuando cambia el contenido, para que el host purgue solo esos documentos. Con esa invalidación, un cambio editorial en los metadatos SEO llega al front-end sin esperar a que caduque la caché.
Guías relacionadas sobre SEO en WordPress headless
Este artículo complementa el pilar del servicio de WordPress headless. Para el momento de decidir, consulta WordPress headless, Next.js vs Astro 2026. Para la cuestión más amplia de la visibilidad, incluidas las citas en LLM, la guía de visibilidad en IA y LLM es nuestra referencia de lo que entregamos en AEO y GEO sobre estas bases SEO.







