WordPin frente al ordenador y el plugin de caché en WordPress que muestra contenido distinto a Googlebot

El plugin de caché y el HTML incompleto · WordPin 002

🎧 Escuchar: El plugin de caché y el HTML incompleto · WordPin 002

El sitio llevaba más de un año funcionando sin incidencias graves. Un medio digital con contenido editorial y patrocinado, tráfico orgánico estable, publicación regular y una infraestructura bastante estándar sobre WordPress. El equipo de contenido publicaba, el tráfico respondía y las métricas de rastreo en Search Console no mostraban nada fuera de lo normal. Hasta que un día alguien del equipo SEO revisó el renderizado de varias URLs en la herramienta de inspección de Google y vio algo que no tenía sentido: los bloques de contenido patrocinado y algunos elementos de plantilla no aparecían en el HTML que Google estaba leyendo. En el navegador, la página se veía perfecta.

Lo primero que pensé fue en un fallo de renderizado del lado de Google, algo puntual. Pero al revisar más URLs, el patrón se repetía. Las páginas que un usuario veía completas llegaban a Google parcialmente vacías. El plugin de caché era parte del stack técnico del sitio, pero en ese momento no era sospechoso de nada. El sistema llevaba meses configurado y nadie había tocado sus reglas recientemente.

Algo entre el servidor y el bot estaba filtrando contenido. La pregunta era qué capa lo estaba haciendo y por qué solo afectaba a las peticiones de rastreo. Esto no encajaba con un fallo de tema ni con un error de servidor clásico.

Imagen de un sello que representa el archivo 002 de WordPin ABIERTO
Ficha del expediente Nº: 002

Operario: WordPin

Expediente del caso: El plugin de caché y el HTML incompleto

Empresa o negocio: Medio digital con contenido patrocinado

Archivado en: Plugins, temas y conflictos en WordPress

Nivel: Avanzado

El plugin de caché bajo sospecha

El primer paso fue confirmar que el problema era reproducible y no un artefacto de la herramienta de inspección de Google. Abrí varias URLs en el navegador, revisé el código fuente y todo estaba ahí: los bloques editoriales, los módulos de contenido patrocinado, los elementos de sidebar, el footer completo. El HTML del documento tenía todo lo esperado.

Después lancé peticiones con curl usando el user-agent estándar de un navegador de escritorio y obtuve exactamente el mismo HTML completo. Pero cuando cambié el user-agent a Googlebot, la respuesta era distinta. El Content-Length era notablemente menor y al inspeccionar el cuerpo del HTML, faltaban secciones enteras.

curl -s -o /dev/null -w "%{size_download}" -H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://ejemplo.com/articulo-patrocinado/
→ 34218

curl -s -o /dev/null -w "%{size_download}" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" https://ejemplo.com/articulo-patrocinado/
→ 51903

Una diferencia de casi 18 KB en el mismo documento. Eso no era un detalle menor. Significaba que el HTML entregado al bot era estructuralmente diferente al que veía un usuario real. La siguiente pregunta era dónde se producía ese recorte.

Primera pista descartada: condiciones de plantilla en el tema

La primera hipótesis fue que el tema tuviera alguna condición PHP que ocultara bloques según el contexto de la petición. Algunos temas mal construidos usan comprobaciones de tipo is_user_logged_in() o incluso sniffers de user-agent dentro de los archivos de plantilla para mostrar u ocultar contenido.

Revisé los archivos single.php, header.php, footer.php y los templates parciales de los bloques patrocinados. No había ninguna condición que dependiera del user-agent ni del tipo de petición. Todo el contenido se renderizaba sin restricciones a nivel de plantilla.

Aun así, no quería cerrarlo por intuición. Hice bypass de caché con un parámetro aleatorio en la URL y repetí la petición con user-agent de Googlebot. Con bypass, el HTML llegaba completo. Si el tema estuviera filtrando por user-agent, el HTML generado antes de cualquier capa intermedia ya habría nacido incompleto. No era el tema. El patrón apuntaba a otra capa.

El plugin de caché y la respuesta que cambiaba según quién preguntaba

La segunda hipótesis apuntaba a un problema de renderizado JavaScript. Algunos contenidos dinámicos dependen de JS para inyectarse en el DOM y Googlebot, aunque ejecuta JavaScript, no siempre lo procesa con la misma fidelidad que un navegador completo. Pero esto no aplicaba aquí. El contenido que faltaba no era inyectado por JS: estaba presente en el código fuente HTML cuando la petición se hacía sin pasar por la capa de caché del sitio. Confirmé esto revisando el HTML crudo con curl añadiendo un parámetro ?nocache=1 que forzaba la generación dinámica. El contenido aparecía completo. El problema no era de renderizado en cliente, sino de lo que el servidor entregaba desde su sistema de caché.

Llegado a este punto, las cabeceras HTTP empezaban a importar más que el HTML visible. Usé DevTools y peticiones curl con la flag -I para comparar cabeceras entre una petición normal y otra con user-agent de bot.

# Petición con user-agent de navegador
X-Cache: HIT
X-Cache-Group: normal
Cache-Control: max-age=86400

# Petición con user-agent de Googlebot
X-Cache: HIT
X-Cache-Group: bot
Cache-Control: max-age=604800

Ahí estaba la huella.

No era una diferencia de renderizado. No era un shortcode caprichoso. No era JavaScript dejando contenedores vacíos. El servidor estaba colocando a Googlebot en otro grupo de caché: bot. Y ese grupo no solo era distinto; además conservaba la respuesta durante siete días, frente a las veinticuatro horas de la versión normal.

Dos visitantes pedían la misma URL. WordPress entregaba dos versiones cacheadas distintas. Una representaba la página actual. La otra llevaba días contando una versión antigua del sitio.

El servidor puede responder con 200 y aun así estar entregando la respuesta equivocada.

El HTML que Google recibía llevaba días sin representar la página real

La causa estaba en una configuración avanzada del plugin de caché. El sitio usaba WP Rocket con una regla modificada para segmentar la entrega por tipo de user-agent. El problema no era el plugin en sí, sino una configuración heredada que generaba dos versiones HTML de la misma página: una para usuarios reales y otra para bots de rastreo. La intención original, según el historial de cambios, era servir una versión más ligera a los bots. Pero el efecto real era otro.

La versión cacheada para bots se había generado en un momento en el que ciertos bloques de contenido patrocinado aún no estaban publicados o la plantilla tenía un estado intermedio. Y como el TTL asignado a ese grupo era de siete días, esa caché HTML obsoleta seguía sirviéndose sin que nadie lo supiera. Cada vez que el equipo editorial publicaba nuevo contenido o actualizaba bloques, la versión de usuario se regeneraba con normalidad, pero la de bot no. La purga automática del plugin solo afectaba al grupo de caché principal.

Revisé el directorio de caché en el servidor para confirmar la estructura:

/wp-content/cache/wp-rocket/ejemplo.com/articulo-patrocinado/
├── index.html              ← versión usuario (actualizada)
├── index.html_gzip         ← versión usuario comprimida
└── bot/
    ├── index.html          ← versión bot (7 días sin regenerar)
    └── index.html_gzip

La carpeta bot/ mantenía un HTML que ya no correspondía al estado actual de la página. Le faltaban los bloques div.sponsored-block, parte del sidebar y algunos elementos del footer que se habían añadido después de la última regeneración de esa versión. El problema no era que la página no cargara. El problema era que cargaba distinta para quien tenía que decidir si merecía estar en el índice.

El impacto en el rastreo era directo: Google Search Console mostraba que varias URLs con contenido patrocinado estaban catalogadas como rastreadas pero no indexadas, probablemente porque el contenido que Google veía era demasiado escaso o redundante respecto a otras páginas del sitio. Los artículos que en navegador tenían mil doscientas palabras con módulos visuales, llegaban a Googlebot como esqueletos de trescientas palabras con huecos en la plantilla.

Esto también explicaba por qué el problema no se había detectado antes. Ninguna alerta del plugin de caché avisaba de esto. El sitio seguía funcionando para los usuarios, las métricas de velocidad eran buenas y el tráfico orgánico general no había caído de forma brusca. Solo las URLs más afectadas habían dejado de indexarse gradualmente, sin provocar un error visible.

Limpiar la entrega sin dejar huecos en el rastreo

Lo primero que parecía lógico era desactivar por completo el plugin de caché para eliminar cualquier riesgo de que el problema se repitiera. Pero en un medio digital con picos de tráfico frecuentes y contenido patrocinado que depende de tiempos de carga competitivos, apagar la caché en producción no era una opción real. El sitio necesitaba esa capa para sostener la concurrencia sin degradar la experiencia ni disparar los tiempos de respuesta del servidor.

La otra alternativa inmediata era purgar toda la caché y dejar que se regenerara desde cero. Eso habría resuelto las versiones obsoletas, pero sin corregir la regla de segmentación por user-agent, el problema se habría reproducido en pocos días. La purga sola no servía si la configuración seguía creando dos mundos paralelos de HTML.

Corregir las reglas sin desactivar la caché en producción

La solución fue intervenir directamente en la configuración de caché de WP Rocket. Primero identifiqué la regla que activaba la segmentación por user-agent. Estaba definida a nivel de configuración avanzada del plugin, dentro de una directiva que separaba bots del tráfico habitual. Eliminé esa condición para que el servidor sirviera exactamente el mismo HTML cacheado a cualquier visitante, independientemente de su user-agent.

Después ejecuté una purga completa de todas las versiones almacenadas, incluyendo las carpetas específicas del grupo bot que habían quedado fuera de las purgas automáticas anteriores. Eliminé manualmente el directorio /wp-content/cache/wp-rocket/ entero para asegurar que no quedaran restos de archivos huérfanos y dejé que el plugin regenerara la caché limpia con las nuevas reglas unificadas.

Para validar que la corrección funcionaba, lancé nuevas peticiones con curl alternando user-agents y comparé los resultados. El Content-Length era ahora idéntico en ambos casos. Las cabeceras ya no mostraban la separación en grupos y la respuesta HTML contenía todos los bloques de contenido, plantilla y estructura que antes faltaban en la versión de bot.

curl -s -o /dev/null -w "%{size_download}" -H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://ejemplo.com/articulo-patrocinado/
→ 52071

curl -s -o /dev/null -w "%{size_download}" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" https://ejemplo.com/articulo-patrocinado/
→ 52071

Finalmente, usé la inspección de URL en Search Console para forzar un nuevo rastreo de las páginas más afectadas y verificar que Google recibía el HTML completo. En las semanas siguientes, las URLs que habían caído del índice empezaron a reaparecer progresivamente. No hubo recuperación instantánea, pero el flujo de indexación se normalizó sin intervenciones adicionales.

Añadí una comprobación periódica al flujo de mantenimiento del sitio: una vez a la semana, una petición automatizada con user-agent de Googlebot contra las URLs principales para comparar el tamaño de respuesta con la versión de usuario. Si la diferencia supera un umbral, salta un aviso. Es una regla de caché que ahora forma parte de la monitorización habitual del proyecto.

Imagen de un sello que representa el archivo de WordPin 002 COMPLETADO

💡 Qué he aprendido

Este caso me enseñó que un plugin de caché puede funcionar perfectamente para los usuarios y al mismo tiempo estar saboteando el rastreo sin dejar ni una sola alerta visible. La segmentación por user-agent parecía una optimización razonable cuando se configuró, pero con el tiempo se convirtió en una trampa silenciosa. Nadie revisa lo que el bot recibe si el navegador muestra todo bien.

Aprendí a no confiar en el HTML que veo en mi pantalla como representación de lo que Google está procesando. Son dos realidades que pueden divergir de formas sutiles, especialmente cuando hay una capa de caché intermedia con reglas de entrega diferenciadas. Desde este caso, incluyo la comparación de respuestas por user-agent como parte de cualquier auditoría técnica que implique caché.

La caché no inventa problemas. Muchas veces solo conserva los antiguos. Y si nadie comprueba qué versión está conservando para cada tipo de visitante, el problema puede vivir meses sin que nadie lo vea.

Cuaderno de WordPin

Forense WordPress

Archivo Nº 002: El plugin de caché y el HTML incompleto

🧩 Notas técnicas

⬋⬋

Medio digital con contenido patrocinado que mostraba páginas completas en el navegador pero entregaba HTML incompleto a Googlebot. La inspección de URLs en Search Console reveló que varias páginas clave aparecían como rastreadas pero no indexadas, sin errores visibles.

La causa fue una regla de segmentación por user-agent en WP Rocket que mantenía versiones de caché separadas para bots, con un TTL extendido que impedía su regeneración. La corrección eliminó la segmentación, purgó las versiones obsoletas y unificó la entrega HTML para todos los visitantes.

🔎 Pistas detectadas

⬋⬋

  • Diferencia de casi 18 KB en el tamaño de respuesta HTML entre peticiones con user-agent de navegador y de Googlebot.
  • Cabecera X-Cache-Group con valor bot en peticiones de rastreo, frente a normal en peticiones de usuario.
  • TTL de siete días en la caché de bot frente a veinticuatro horas en la versión estándar.
  • Directorio /cache/wp-rocket/.../bot/ con archivos HTML generados días antes del contenido publicado actual.
  • URLs con contenido patrocinado catalogadas como rastreadas pero no indexadas en Search Console.

🚫 Errores en WordPress detectados

⬋⬋

  • Segmentación de caché por user-agent que generaba versiones HTML paralelas: el bot recibía un documento diferente al usuario real sin que ninguna métrica lo reflejara.
  • TTL excesivo en el grupo de caché de bots que impedía la regeneración automática: las páginas acumulaban días de desfase respecto al contenido publicado.
  • Purga automática del plugin limitada al grupo principal de caché: las versiones de bot no se purgaban con las actualizaciones de contenido.
  • Ausencia de monitorización de la respuesta HTML por tipo de visitante: nadie verificaba que Googlebot recibiera el mismo contenido que un navegador.
  • Bloques de contenido patrocinado y elementos de plantilla ausentes en la versión cacheada de bot: el HTML servido al rastreador tenía estructura válida pero contenido insuficiente para indexación.

📘 Glosario de WordPress

⬋⬋

  • Caché HTML: Copia estática del documento generado por WordPress que el servidor entrega sin ejecutar PHP en cada petición.
  • User-agent: Cadena de identificación que el navegador o bot envía al servidor para indicar quién realiza la petición.
  • TTL (Time to Live): Tiempo máximo que una versión cacheada se considera válida antes de necesitar regeneración.
  • Purga de caché: Eliminación forzada de las copias estáticas almacenadas para que el sistema genere versiones nuevas del contenido.
  • Content-Length: Cabecera HTTP que indica el tamaño en bytes del cuerpo de la respuesta entregada al cliente.

📏 Mini guía práctica

⬋⬋

  1. Compara el tamaño de respuesta HTML usando curl con user-agent de navegador y de Googlebot para detectar diferencias en la entrega.
  2. Revisa las cabeceras HTTP de respuesta en busca de indicadores de segmentación de caché como X-Cache-Group o variantes similares.
  3. Verifica que la purga automática del plugin de caché afecte a todos los grupos de versiones almacenadas, no solo al principal.
  4. Inspecciona el directorio físico de caché en el servidor para confirmar si existen carpetas separadas por tipo de visitante con archivos obsoletos.
  5. Establece una comprobación periódica automatizada que compare la respuesta HTML servida a bots frente a usuarios reales y alerte si hay diferencias significativas.

Expedientes y Archivos

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Respetamos tu privacidad. Los datos que proporciones se utilizarán únicamente para gestionar y mostrar tu comentario, así como para prevenir el uso indebido o el spam. Puedes consultar más información en nuestra política de privacidad.

Subir