# Ultra guía del renderizado en Google: qué ejecuta Googlebot, cuándo hace la captura y cómo comprobarlo

*Qué hace Google con tu página desde que la rastrea hasta que la indexa: cómo la renderiza, cuánto espera, qué ve y qué no, y cómo comprobarlo en tu web. Con pruebas que puedes repetir, el misterio de los 5 segundos explicado y lo que pasa fuera de Google, con Bing y la IA.*

Por Carlos Sánchez, con ayuda de IA

Publicado el 12 de octubre de 2026, 12:36 · Revisado el 14 de octubre de 2026, 15:42

<https://seomanal.es/tecnico/renderizado/guia-renderizado-google/>

![La palabra Rendering sobre una tira de capturas de una página que va cargando, de la pantalla en blanco a la página completa](https://seomanal.es/uploads/articulos/2-6378383a71a09244.webp)

Google no indexa el HTML que escribes. Indexa **el DOM que queda después de cargar tu página en su propio navegador**, ejecutar tu JavaScript y aplicar tu CSS. De esa diferencia sale casi todo lo que cuesta explicar en una auditoría: el noindex que nadie puso, el texto que está en la página y Google no ve, la captura de Search Console con un bloque enorme donde debería haber un artículo.

Llevo escribiendo sobre esto desde 2023 y haciendo mis propias pruebas: contadores, metaetiquetas inventadas, páginas con marcas de píxeles. En esta guía junto lo que he aprendido con ellas, lo que documenta Google y lo que han medido otros, ordenado para que se entienda de principio a fin: primero qué es renderizar, después qué hace Google con tu página y, al final, cómo comprobarlo en la tuya.

Es larga. Si solo quieres auditar tu web, ve directo al [checklist](#checklist).

## La versión corta

- Google indexa el HTML renderizado: el DOM tal como queda después de ejecutar tu JavaScript. Lo que el script añade cuenta igual que lo que venía del servidor, y lo que el script quita desaparece.
- Renderiza todas las páginas que responden con un 200 y no llevan noindex, tengan o no JavaScript, y casi siempre lo hace segundos o minutos después de rastrearlas.
- Según las pruebas publicadas (Google no lo documenta), captura la página cuando su reloj marca unos 5 segundos, pero ese reloj no es el tuyo: lo que retrasas con un temporizador de más de 5 segundos se queda fuera, y lo que tarda porque una API va lenta puede entrar.
- No hace clic ni scroll, y no recuerda nada entre una carga y la siguiente. Para ver lo de abajo estira la ventana, y por eso un bloque a 100vh (tan alto como la ventana) sin tope se estira con ella.
- Antes de renderizar ya ha leído el HTML del servidor, con su código de estado, su noindex y su canonical. Un noindex que venga ahí no se quita con JavaScript, y con la canonical es mejor no arriesgarse.
- Fuera de Google casi nadie ejecuta tu JavaScript. En mis pruebas, el índice de Bing no refleja lo que cambia el script, y la mayoría de los chats de IA leen solo el HTML cuando les das una URL. Un navegador con IA sí lo ejecuta, porque es un navegador.
- Todo se comprueba en el mismo sitio: inspección de URL de Search Console, «Ver página rastreada». Si tu contenido está en ese HTML, Google lo tiene.

### Seis palabras que vas a leer mucho

- **HTML inicial** (o código fuente): lo que envía tu servidor. Es lo que ves con Ctrl+U.
- **DOM**: el árbol que el navegador construye a partir de ese HTML y que JavaScript puede cambiar. Es lo que ves en el inspector de elementos.
- **HTML renderizado**: el DOM convertido otra vez en HTML en un momento dado. Es lo que Google indexa.
- **WRS** (Web Rendering Service): el servicio de Google que abre tu página en un Chromium sin pantalla, ejecuta el JavaScript y devuelve el HTML renderizado.
- **Snapshot**: el momento en que el WRS da la página por terminada y se queda con el DOM. No es una imagen.
- **CSR y SSR**: renderizar en el navegador del usuario (client-side) o en el servidor (server-side). Lo que importa para el SEO es quién monta el HTML y cuándo.

### Qué se sabe a ciencia cierta, qué está en disputa y qué conviene revisar

No todos los datos que circulan sobre el renderizado valen lo mismo. En esta tabla, «a ciencia cierta» quiere decir que lo documenta Google o que varias pruebas independientes han medido lo mismo; «en disputa», que hay fuentes serias que no coinciden o que falta una prueba publicada; y «conviene revisar», que el dato sale de una sola prueba, de una prueba antigua o de algo que cambia rápido.

| Dato | Lo que se sabe | Estado | De dónde sale |
| --- | --- | --- | --- |
| Qué indexa Google | El HTML renderizado: el DOM que queda tras ejecutar el JavaScript, no el código fuente | A ciencia cierta | Documentación de Google |
| Qué páginas renderiza | La documentación y Zoe Clifford (2024) dicen que todas las que responden 200 y no llevan noindex, tengan o no JavaScript. Martin Splitt dijo en 2023 que, si ya han detectado que una página es mala, se saltan el renderizado | En disputa | Documentación de Google y Search Off the Record (2024) frente al webinar de Duda (2023); medición de Vercel y MERJ |
| Espera entre el rastreo y el renderizado | Segundos o minutos para la gran mayoría de las páginas. Las medianas que se han dado van de 5 segundos (Google, de viva voz) a 10 (una medición externa) | A ciencia cierta | Google; Vercel y MERJ |
| El 10 % más lento de esa espera | Minutos según Google; unas 3 horas en el percentil 90 y unas 18 en el 99 según una medición externa | En disputa | Google frente a Vercel y MERJ |
| Cuándo captura la página | Cuando su reloj interno llega a unos 5 segundos. Ese reloj se para mientras espera a la red y corre deprisa cuando la página no hace nada: un temporizador de más de 5 segundos se queda fuera y una API lenta puede entrar | Conviene revisar | Mis pruebas, las de Dave Smart y las de Tony McCreath (Website Advantage). Google no lo documenta |
| Espera máxima en tiempo real | Unos 30 segundos en la indexación real y unos 50 en la prueba en vivo. Google no da la cifra | Conviene revisar | Pruebas de Dave Smart y de Tony McCreath |
| Caché de JS y CSS | Hasta 30 días, sin atender a tus cabeceras de caché | A ciencia cierta | Google (Crawling December) |
| Cuánto descarga Googlebot | 2 MB por URL y por recurso, medidos sin comprimir y con las cabeceras HTTP incluidas (64 MB en los PDF). Lo que queda detrás no existe | A ciencia cierta | Documentación de Googlebot (3 de febrero de 2026) y post de Gary Illyes (31 de marzo de 2026); avisa de que puede cambiar |
| Clics, scroll y estado | No hace clic ni scroll y no guarda cookies ni almacenamiento entre cargas | A ciencia cierta | Documentación de Google |
| Cómo ve lo que queda abajo | Agranda la ventana (viewport expansion) en lugar de hacer scroll | A ciencia cierta | Google; medido por Tony McCreath |
| Medidas de la ventana y de la captura | 981 × 1742 px al empezar en móvil y más de un millón de píxeles de alto en una prueba. La captura de Search Console corta a 1750 px | Conviene revisar | Tony McCreath; mi prueba con Mario Mangas |
| Hero a 100vh sin max-height | Se estira con la ventana y deforma la captura. Que además perjudique la indexación no está probado | En disputa | Mi experiencia en proyectos frente a Google |
| Quitar un noindex con JavaScript | No funciona. Añadirlo con JavaScript, sí | A ciencia cierta | Google; prueba de Dave Smart |
| ¿Se renderiza una página con noindex? | Google «puede» saltárselo, y se le ha visto ejecutar JavaScript en páginas noindex. Lo más habitual es que no, más si siempre estuvo en noindex | Conviene revisar | Documentación (desde diciembre de 2025); prueba de Dave Smart |
| Canonical hacia otra URL en el HTML inicial | Google solo dice que las canonical en conflicto, o cambiadas por JavaScript, pueden dar resultados inesperados. En mi experiencia, la página ni llega a renderizarse | En disputa | Documentación frente a mi experiencia, sin experimento publicado |
| Enlaces que solo existen por JavaScript | Se descubren más tarde, sobre todo en sitios nuevos. Un sitemap al día reduce mucho la diferencia | A ciencia cierta | Onely; Vercel y MERJ |
| Renderizado dinámico | Google lo desaconseja: es un parche, no una solución | A ciencia cierta | Documentación de Google |
| Web workers | Un worker sencillo se renderizó; los que pedían datos o tardaban más de un segundo, no | Conviene revisar | Prueba de Dave Smart de 2021, sin repetir |
| Bing | Dice que renderiza. En mis pruebas, su índice no refleja los cambios hechos por JavaScript | En disputa | Directrices de Bing frente a mi prueba |
| Chats de IA cuando les das una URL | La mayoría lee solo el HTML. Qwen ejecutó el JavaScript; DeepSeek, Kimi y Mistral, a veces | Conviene revisar | Mis pruebas (junio de 2026) |
| Rastreadores de IA | Descargan los ficheros JS, pero no los ejecutan. La excepción es AppleBot; Gemini va aparte | Conviene revisar | Vercel y MERJ (diciembre de 2024) |
| Navegadores y agentes con IA | Abren la página en un navegador de verdad, así que el JavaScript se ejecuta. Solo he probado uno | A ciencia cierta | Google; mi prueba con el navegador de Claude Code |
| AI Overviews y AI Mode | Recuperan las páginas del índice de Google: si tu página está indexada y la propiedad no está excluida de las funciones de IA en Search Console, pueden usarla | A ciencia cierta | Documentación de Google; ayuda de Search Console (control desplegado el 31 de agosto de 2026) |

Cada fila se explica en su capítulo:

1. [Qué es renderizar y por qué el HTML que escribes no es el que cuenta](#que-es-renderizar)
2. [Cómo renderiza Google: de la cola de rastreo al WRS](#como-renderiza-google)
3. [Qué ve Google y qué no: clics, scroll, ventana, recursos y caché](#que-ve-google)
4. [El HTML inicial manda: noindex, canonical, códigos de estado y soft 404](#html-inicial)
5. [El misterio de los 5 segundos: cuánto tarda y cuánto espera Google](#cinco-segundos)
6. [Estrategias de renderizado: CSR, SSR, estático, ISR, hidratación y renderizado dinámico](#estrategias)
7. [Cambiar lo que Google lee sin tocar el código de la web](#controlar)
8. [Cómo comprobar lo que Google ha renderizado](#herramientas)
9. [Fuera de Google: Bing, los chats de IA, sus rastreadores y los navegadores con IA](#fuera-de-google)
10. [Checklist para auditar el renderizado de tu web](#checklist)
11. [Preguntas abiertas](#abierto)

## Qué es renderizar y por qué el HTML que escribes no es el que cuenta

El HTML que se escribe no es el HTML que termina viendo el usuario. Por el camino puede haber un CDN, un CMS, un lenguaje de servidor o un JavaScript que lo cambie todo.

> El renderizado es el proceso de convertir el HTML en píxeles.

Es la definición con la que Martin Splitt abrió sus charlas en [SEO on the Beach 2023](https://sanchezdonate.net/articulo/noticia/martin-splitt-renderizacion/) y en [Andalu-SEO 2026](https://andalu-seo.es/ponente/2026/martin-splitt). A un buscador, sin embargo, los píxeles le importan poco: le interesa lo que el navegador construye por el camino, y lo vas a ver paso a paso.

### Del HTML al DOM

Ni los navegadores ni Google leen el HTML tal cual. Lo convierten en un árbol de nodos, el DOM, y trabajan con ese árbol (lo expliqué con más calma en [Renderizado en el SEO](https://sanchezdonate.net/articulo/renderizado-en-el-seo/)).

![Esquema con un documento HTML a la izquierda y, a la derecha, el mismo documento convertido en árbol DOM con sus nodos html, head, body, h1, p y table](https://seomanal.es/uploads/articulos/2-452ca5d529d85654.webp)
*El mismo documento como texto y como árbol DOM. El navegador trabaja con el árbol, no con el texto.*

El DOM es el modelo que el navegador se hace de la página: se construye a partir del HTML y recoge todo lo que JavaScript cambie después. Una forma de verlo es una receta. El HTML, el CSS y las imágenes son los ingredientes, el navegador cocina y el DOM es la masa de la que salen las tortitas.

Una casa sirve para entender otra cosa: por qué el mismo código no da siempre la misma página. Si la describes como si fuera HTML, con dos pisos, tres ventanas y una chimenea, queda así:

```
<house floors="2">
   <window />
   <window />
   <window />
   <chimney />
</house>
```

Dale esa descripción a diez personas y dibujarán diez casas distintas, todas correctas. Con una web pasa lo mismo: el mismo código produce resultados distintos según el dispositivo, el tamaño de la pantalla o la orientación. No existe «la página» como algo fijo. Existe un código y lo que cada navegador, o cada bot, construye con él.

En el DOM no queda ninguna marca de si un contenido venía en el HTML inicial o lo inyectó JavaScript, y eso ahorra muchas discusiones: si está en el árbol cuando Google lo captura, es indexable y puede posicionar.

### El recorrido dentro del navegador, en cinco pasos

El DOM es solo una parada. Desde que llega el HTML hasta que ves algo en pantalla, el navegador pasa por cinco pasos:

1. **Parseo:** lee el HTML y el CSS que le han llegado.
2. **DOM y CSSOM:** con el HTML construye el DOM y con el CSS su equivalente para los estilos, el CSSOM.
3. **Árbol de renderizado:** la combinación de DOM y CSSOM, solo con lo que se va a ver.
4. **Layout:** calcula dónde va cada cosa y qué tamaño tiene en pantalla.
5. **Pintado:** ahora sí, los píxeles.

En una web real estos pasos se ven en el panel de rendimiento de Chrome DevTools, cada uno con su color:

![Gráfico de rendimiento de Chrome DevTools entre 500 y 1500 milisegundos: un tramo amarillo de JavaScript seguido de un bloque lila de layout mucho más largo y, al final, picos verdes de pintado](https://seomanal.es/uploads/articulos/2-c0a9a5f824c2e450.webp)
*El panel de rendimiento de DevTools. Azul: carga y parseo del HTML. Amarillo: ejecución de JavaScript. Lila: cálculo de estilos y layout. Verde: pintado.*

En este ejemplo el JavaScript crea más HTML, y por eso vuelve a aparecer azul después del amarillo. Detrás viene un bloque lila de unos 700 milisegundos: el navegador recalculando estilos y colocando cada cosa en su sitio. Ese bloque no tiene por qué ser culpa del JavaScript. Una página sin una sola línea de JS puede dar un gráfico parecido si el HTML y el CSS están mal planteados.

¿Con qué parte de ese recorrido se queda Google? Con el contenido y con la geometría. Para indexar, según contó Martin Splitt en [Andalu-SEO 2026](https://andalu-seo.es/ponente/2026/martin-splitt) (la organización publicó un resumen y las diapositivas), usa el DOM, que le dice qué hay, y el árbol de renderizado con su layout, que le dice qué tamaño tiene cada cosa y dónde queda. Los píxeles del último paso no los necesita.

Por eso **el snapshot que toma Google es del DOM, no una imagen**. Lo que se indexa es el HTML renderizado. La captura de pantalla que enseña Search Console está hecha para que la miremos nosotros. Cuándo se toma ese snapshot da para un capítulo entero: [el misterio de los 5 segundos](#cinco-segundos).

### Código fuente frente a DOM final: míralo en tu propia web

Los dos HTML se ven en diez segundos. El primero es el código fuente: pulsa Ctrl+U o escribe `view-source:` delante de la URL. Es lo que ha enviado el servidor, antes de que se ejecute nada.

![Código fuente de un artículo del blog de Ahrefs abierto con view-source en Chrome](https://seomanal.es/uploads/articulos/2-cb720b1439e4885a.webp)
*El código fuente (Ctrl+U) de un artículo del blog de Ahrefs: lo que envía el servidor.*

El segundo es el DOM final: clic derecho, «Inspeccionar» y pestaña Elementos. Es el árbol vivo, con todo lo que JavaScript ha añadido, quitado o cambiado desde que llegó el HTML.

![Panel Elementos de Chrome DevTools sobre la misma página, con el árbol DOM desplegado y un div de progreso de lectura con un ancho en línea del 13,3312 %](https://seomanal.es/uploads/articulos/2-8398a10834fa7cee.webp)
*La misma página en el inspector de elementos: el DOM después de ejecutarse el JavaScript.*

En la captura hay un ejemplo pequeño. La barra de progreso de lectura lleva un `style="width: 13.3312%"`, un valor que nadie escribe a mano con cuatro decimales: lo va cambiando un script mientras hago scroll.

La prueba rápida es buscar una frase de tu contenido principal en el código fuente. Si no está ahí y sí en el inspector, la está poniendo JavaScript, y para que Google la lea dependes de que la renderice.

Ten en cuenta que el DOM de tu inspector es el de tu navegador, con tu pantalla, tus cookies y tus clics. El que ha construido Google solo se ve en la inspección de URL de Search Console; cómo leerla está en [el capítulo de las herramientas](#herramientas).

### Cliente y servidor: quién ejecuta qué (y por qué Java no es JavaScript)

![Ilustración de un servidor que envía archivos HTML, CSS y JS a un ordenador y a un móvil](https://seomanal.es/uploads/articulos/2-d452ce78fc6a31dd.webp)
*El servidor envía HTML, CSS, JS y multimedia. Lo que pase después depende del navegador.*

El servidor siempre le envía al navegador lo mismo: HTML, CSS, JS y archivos multimedia. Lo que cambia es quién ha hecho el trabajo antes de que lleguen.

Los lenguajes de servidor (PHP, Python, .NET, Ruby, Java, Liquid en Shopify) se ejecutan en el ordenador del hosting. Hacen allí su parte del renderizado y al navegador le llega el código ya procesado. Por eso en tu Ctrl+U nunca verás PHP: verás el HTML que PHP ha generado.

JavaScript actúa después y en otro sitio. Llega el HTML, el HTML le dice al navegador que ejecute los scripts y estos empiezan a modificar el propio HTML. Yo lo llamo «lenguaje parche». Y quien lo ejecuta es el visitante desde su móvil o su ordenador, o el bot que rastrea. Rastrear una web que depende de JavaScript lleva bastante más tiempo que rastrear una que no, y el tiempo son recursos y dinero.

Y aquí va la aclaración de siempre: [Java no es JavaScript](https://sanchezdonate.net/articulo/curiosidades/java-javascript-seo/). Java es un lenguaje de servidor más, de los que entregan el HTML ya hecho. JavaScript es el único lenguaje de programación que leen, procesan y ejecutan los navegadores. El nombre se parece porque en su día JS quiso aprovechar el tirón de Java, pero son tan distintos como el rumano y el romaní.

JavaScript también puede trabajar en el servidor, con Node.js o con un framework que renderice allí (SSR); lo verás en [las estrategias de renderizado](#estrategias).

### Plantilla más datos

Da igual que uses WordPress, Shopify o un desarrollo propio: una web son plantillas y datos. Piensa en una página de producto. La plantilla es igual para todos los productos y los datos viven en una base de datos. Alguien tiene que juntarlos, y puede hacerlo en tres momentos: antes de que nadie pida la página, en el servidor cuando llega la petición o en el navegador del usuario con JavaScript. Un WordPress los junta en el servidor con PHP; un framework de JavaScript que renderiza en el cliente (CSR), en el navegador.

Renderizar, para el SEO, es decidir dónde, cuándo y cómo se juntan. Cuanto más tarde y más lejos del servidor se haga, más dependes de que quien visita la página, sea una persona o un bot, ejecute bien el JavaScript, y más se separa tu Ctrl+U de tu DOM final.

## Cómo renderiza Google: de la cola de rastreo al WRS

Googlebot no es un navegador que entra en tu web, mira y se va, sino una cadena de colas y servicios en la que el navegador solo aparece en uno de los eslabones. Y es solo el cliente de la Búsqueda dentro de [una plataforma de rastreo compartida](https://developers.google.com/search/blog/2026/03/crawler-blog-post) por la que también pasan Google Shopping o AdSense, cada uno con su user-agent y sus propios límites.

Tener este mapa en la cabeza deshace muchos malentendidos: que si Google elige qué renderiza, que si manda la caché de tu servidor, que si lo que ves en tu Chrome es lo que ve él.

### El recorrido de una URL, paso a paso

1. **Descubrimiento.** La URL llega por enlaces, sitemaps o Search Console.
2. **Cola de rastreo.** Antes de pedir nada, Googlebot mira el robots.txt del host. Si la URL está bloqueada no hay petición HTTP y el recorrido termina ahí.
3. **Rastreo.** Descarga el HTML y nada más, como lo haría un `curl` muy optimizado, y guarda la respuesta con sus tiempos, sus cabeceras y la IP.
4. **Procesamiento.** Se evalúan el código de estado y la canonical, y se extraen los enlaces de los `href`, que vuelven a la cola de rastreo.
5. **Cola de renderizado.** Entran las páginas que han respondido 200 y no llevan noindex.
6. **WRS.** Cuando los recursos de Google lo permiten, un Chromium sin interfaz (headless) abre la página y ejecuta el JavaScript.
7. **Vuelta al procesamiento.** El HTML renderizado es el que se indexa, y de él se vuelven a extraer enlaces para la cola de rastreo.

![Esquema del recorrido de una URL en Google: descubrimiento, cola de rastreo, robots.txt y rastreo; procesamiento, códigos de respuesta, extracción de metaetiquetas, solicitud de renderización y WRS; recolección de señales, índice y SERPs](https://seomanal.es/uploads/articulos/2-4c0138850bd24421.webp)
*Esquema propio, basado en la [ilustración de Jan-Peter Ruhso](https://x.com/JanRuhso/status/1454026389078872065). Es orientativo, no un diagrama oficial de Google.*

El diagrama oficial, más escueto, está en [la documentación de Google sobre JavaScript](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). En el mío hay más cajas de las que Google documenta, y el orden exacto de algunas (dónde se detecta un soft 404, por ejemplo) no está confirmado. Tómalo como un mapa, no como un plano: hay pasos que ocurren a la vez y pasos que se repiten.

Ese Chromium es [evergreen desde 2019](https://developers.google.com/search/blog/2019/05/the-new-evergreen-googlebot): se actualiza poco después de cada versión estable de Chrome. Antes, Googlebot renderizaba con un motor de hace años, y de esa época vienen muchos consejos que ya no aplican.

### El WRS dentro del sistema de indexación

La indexación de Google es un conjunto de servicios que hablan entre ellos, cada uno con su trabajo: el rastreo, el análisis del robots.txt, el de los enlaces, el del contenido, la eliminación de duplicados y el renderizado. El sistema procesa muchas webs a la vez, y una URL puede quedarse por el camino con un código de error mientras la de al lado sigue adelante.

> El Web Rendering Service (WRS) es el servicio que recibe el HTML ya rastreado y sus recursos, y devuelve el DOM renderizado.

![Esquema de qué es el Web Rendering Service desde Google: el HTML rastreado y los recursos entran en el WRS, que devuelve el DOM tree renderizado](https://seomanal.es/uploads/articulos/2-df061a33fad58907.webp)
*Diapositiva de Google, de la ponencia de Martin Splitt en SEO on the Beach 2023.*

Ese DOM es el que ves en la inspección de URL de Search Console y el que Google usa para indexar. Por dentro, el WRS tiene dos partes: el núcleo que renderiza y una caché de recursos. La caché es lo que le permite aguantar el volumen de webs que procesa cada día, y también el origen de buena parte de las diferencias con tu navegador.

Detrás hay una flota de navegadores:

![Diagrama de la orquestación del renderizado en Google: un Renderer con capas de Orchestration, Instrumentation y Metrics Collection, un Pool Manager y varias instancias de Headless Chrome](https://seomanal.es/uploads/articulos/2-db3a65a5a36f2063.webp)
*Diapositiva de Google, de la ponencia de Martin Splitt en SEO on the Beach 2023.*

Una capa de orquestación reparte el trabajo, otra instrumenta los navegadores y otra recoge métricas; un gestor (Pool Manager) mantiene un grupo de Chrome sin interfaz en marcha. Si una instancia se cae, arranca otra y vuelve a lanzar el trabajo (está explicado en [la ponencia de SEO on the Beach 2023](https://www.youtube.com/watch?v=BU8ZvTq4A6M&t=1024s)), así que un renderizado no se queda a medias por un fallo del navegador. En 2023 escribí que esto servía para detectar cloaking, y me equivoqué: relanzar un trabajo que ha fallado no tiene nada que ver con renderizar la misma URL varias veces para comparar.

¿Y si aun así el renderizado falla? Es raro, y en ese caso Google se queda con el HTML del servidor, que es mejor que nada. Cuando falla, suele ser por recursos bloqueados en el robots.txt o por casos límite, según [la medición de Vercel y MERJ](https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process).

### En qué se diferencia el WRS de tu Chrome

El WRS es Chromium, pero no se comporta como el Chrome que tienes abierto:

| Qué | Tu Chrome | El WRS | Fuente |
| --- | --- | --- | --- |
| Red | El navegador descarga cada recurso. | No sale a la red: le pide cada recurso a Googlebot. | [Splitt e Illyes, Crawling December](https://developers.google.com/search/blog/2024/12/crawling-december-resources) (3 de diciembre de 2024) |
| Caché | Obedece tus cabeceras HTTP de caché. | Caché propia de JavaScript y CSS de hasta 30 días, al margen de esas cabeceras. | Crawling December (2024) |
| Estado | Conserva cookies, Local Storage y Session Storage. | Sin estado: los borra entre una carga y otra. | [Guía de problemas de JavaScript](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript) (act. 18 de diciembre de 2025) e Illyes (2026) |
| Imágenes y vídeo | Los descarga. | No los pide. Tampoco las fuentes. | Illyes (31 de marzo de 2026) |
| Shadow DOM | Árboles separados. | Aplana shadow DOM y light DOM en el HTML renderizado. | Documentación de JavaScript (act. 4 de marzo de 2026) |
| Azar y reloj | Math.random() distinto en cada carga y hora real. | Math.random() devuelve siempre la misma secuencia y el reloj está manipulado. | [Experimento de Tony McCreath](https://bigcommerce.websiteadvantage.com.au/tonys-theory-of-googlebot-relativity/), de Website Advantage (pruebas de principios de 2025) |

La primera diferencia es la que más cuesta interiorizar. Como el WRS no sale a la red por su cuenta, cada JS y cada CSS lo pide Googlebot: respeta el robots.txt y gasta crawl budget del hostname que aloja el recurso. Por eso entre un paso y otro puede pasar bastante más tiempo que en un navegador.

La segunda significa que para el WRS no manda tu `Cache-Control` (qué hacer con eso está en [la caché de 30 días](#cache-30-dias)). Y «sin estado» quiere decir que para Google siempre es tu primera visita: si el contenido depende de una cookie o de algo guardado en Local Storage en una carga anterior, no lo va a ver.

La última, la del azar y el reloj, no está documentada, pero se ha medido ([esta prueba](https://bigcommerce.websiteadvantage.com.au/tonys-theory-of-googlebot-relativity/) lo detalla). La explicación más razonable es que Google quiere que dos renderizados de la misma página den el mismo resultado, y ese reloj manipulado es el que explica el [misterio de los 5 segundos](#cinco-segundos).

### El límite de 2 MB y el orden dentro del HTML

Si te suena la cifra de 15 MB, es la que figuraba antes en la documentación. Google la cambió en la [página de Googlebot](https://developers.google.com/search/docs/crawling-indexing/googlebot) el 3 de febrero de 2026, y el 31 de marzo Gary Illyes explicó [cómo se cuentan esos bytes](https://developers.google.com/search/blog/2026/03/crawler-blog-post). Los números son estos:

- Googlebot descarga como máximo **2 MB por URL**, PDF aparte. El límite se aplica sobre los datos sin comprimir (no sobre lo que viaja con gzip o Brotli) e incluye las cabeceras HTTP.
- En los PDF el límite es de 64 MB.
- Cualquier otro rastreador de Google que no declare un límite propio usa 15 MB por defecto.
- Los rastreadores de imagen y vídeo tienen umbrales que cambian según el producto.

Cuando un HTML pasa de 2 MB, Google no rechaza la página: corta la descarga ahí y entrega ese trozo a indexación y al WRS como si fuera el fichero completo. Lo que queda detrás ni se descarga, ni se renderiza, ni se indexa.

Cada recurso que pide el WRS tiene su propio contador de 2 MB, independiente del HTML. No suman, pero tampoco se libran: la documentación aplica a cada recurso el mismo límite que al HTML, y el post de Illyes avisa de que el WRS solo ejecuta el código que el rastreador haya llegado a descargar. Un bundle de JavaScript de más de 2 MB llegaría cortado.

A la inmensa mayoría de las webs esto no les afecta: 2 MB de HTML es muchísimo. Lo que sí depende de ti es el orden. Lo que empuja el contenido por debajo del corte son las imágenes en base64 incrustadas, los bloques enormes de CSS o JavaScript en línea y los megabytes de menús al principio del documento. De ahí dos reglas:

- El CSS y el JavaScript pesados, a ficheros externos, que se descargan aparte y tienen su propio límite.
- Lo crítico, arriba: title, metaetiquetas, canonical, elementos `link` y los datos estructurados esenciales. Si caen por debajo del corte, para Googlebot no existen.

Google avisa de que el límite puede cambiar.

### ¿Renderiza Google todas las páginas?

Eso dice la documentación: todas las que responden 200 y no llevan noindex, tengan JavaScript o no. Y lo dijo Zoe Clifford, del equipo de renderizado, en [Search Off the Record](https://www.youtube.com/watch?v=XBT_DUzUbOI&t=264s) el 11 de julio de 2024, cuando le preguntaron cómo deciden qué páginas renderizar: «We just render all of them as long as they're HTML». Tampoco hay sitios a los que se les «apague» el renderizado durante semanas: [todo va a la cola](https://www.youtube.com/watch?v=0iHDk7uqByI&t=1489s), según Martin Splitt en la entrevista con Olga Zarr de mayo de 2024.

Lo respalda una medición externa. Vercel y MERJ analizaron más de 100.000 peticiones de Googlebot, casi todas en nextjs.org, y todas las páginas HTML indexables con código 200 acabaron renderizadas, incluida una sin JavaScript. Su alcance es limitado (un mes, prácticamente un solo sitio, y firma quien vende alojamiento para frameworks de JavaScript), pero apunta en la misma dirección que Google.

Google, sin embargo, ha dicho las dos cosas. Un año antes que Clifford, en el [webinar de Duda del 23 de agosto de 2023](https://www.youtube.com/watch?v=-newLrArh6c&t=2166s), Martin Splitt contó que hacen control de calidad en varias fases y que, si ya han detectado antes que una página es mala, se saltan el renderizado: «what's the point?». La documentación va con Clifford. Yo trabajo con esa versión, pero no doy la de Splitt por desmentida, y por eso la tabla del principio marca este dato como en disputa.

Las excepciones documentadas son dos, y Google las redacta en condicional:

- Con un código distinto de 200, por ejemplo un 404, el renderizado podría saltarse.
- Con un noindex en la metaetiqueta o en la cabecera, Google puede saltarse el renderizado y la ejecución de JavaScript. «Puede» no es «siempre»: lo cuento en [el noindex en origen](#noindex-origen).

Un recurso bloqueado por robots.txt no es una tercera excepción: la página se renderiza sin él, con lo que eso suponga.

Lo que sí está claro es que del interés que Google tenga por tu página depende **cuándo** la renderiza y **con qué frecuencia** vuelve. Si además existe el filtro de calidad del que habló Splitt en 2023, afecta a páginas que ya tenían un problema mayor que el renderizado. Según Martin Splitt en [la entrevista de 2024](https://www.youtube.com/watch?v=0iHDk7uqByI&t=1239s), si una página tarda mucho más de lo normal en procesarse, suele ser señal de que le interesa poco ese contenido (lo verás en [la cola de renderizado](#cola)).

## Qué ve Google y qué no: clics, scroll, ventana, recursos y caché

Google captura el DOM que queda después de cargar la página **sin tocar nada**. No hace clic, no pasa el ratón por encima, no hace scroll y no recuerda nada de una visita para la siguiente.

Eso no significa que solo vea lo que aparece en pantalla. Lo que está en el HTML aunque esté oculto, y lo que el JavaScript trae por su cuenta durante la carga, sí entra. Con cada pieza de contenido hay que hacerse la misma pregunta: ¿está en el DOM sin que nadie haga nada?

### Sin clics ni interacción: pestañas, acordeones y muros de pago

Googlebot no hace clic ni interactúa con nada: ni pestañas, ni botones de «ver más», ni avisos de cookies. La prueba más práctica que conozco sobre esto son los [12 experimentos con contenido en pestañas](https://merj.com/blog/12-experiments-for-tabbed-content-seo) de MERJ: una página que Google no había visto nunca, con pestañas de Bootstrap y una frase única por cada forma de meter el contenido, comprobada en 2018 y otra vez en 2021.

| Cómo llega el contenido | 2018 | 2021 |
| --- | --- | --- |
| HTML estático en la pestaña visible (exp. 1) | Indexado | Indexado |
| HTML estático en una pestaña oculta (exp. 2) | Indexado | Indexado |
| Texto traído por AJAX de una API externa durante la carga (exp. 6) | Indexado | Indexado |
| HTML inicial al que ese AJAX sustituye (exp. 5) | No | No |
| Texto de un diccionario JS de la propia página, insertado en la carga (exp. 4) | No | No |
| HTML inicial al que ese diccionario debía sustituir (exp. 3) | Indexado | Indexado |
| Texto que solo llega con un onClick, desde JSON interno o desde una API (exp. 8 y 10) | No | No |
| HTML estático que ese clic habría sustituido (exp. 7 y 9) | Indexado | Indexado |
| iframe con `index,follow` (exp. 11) | Indexado | No |
| iframe con `noindex,nofollow` (exp. 12) | No | No |

Se resume en tres reglas:

- El HTML estático se indexa aunque la pestaña esté oculta.
- El contenido que llega por AJAX durante la carga también, y Google se queda con el resultado final.
- **Lo que depende de un clic no existe para Google**: se queda con lo que había antes del clic.

La tabla tiene dos resultados raros que no tomaría como regla: el texto que salía de un diccionario JavaScript de la propia página no se indexó, y el iframe con `index,follow` dejó de indexarse en 2021. Es una sola página de prueba, comprobada con `site:` y no con la inspección de URL. Lo que sí deja claro es que, si algo te importa, mejor que esté en el HTML.

Lo que Google sí simula es la inactividad. El navegador de Googlebot finge estar ocioso para que salten los eventos que esperan ese momento, como `requestIdleCallback` (lo contó Zoe Clifford en [Search Off the Record](https://www.youtube.com/watch?v=XBT_DUzUbOI&t=1008s) el 11 de julio de 2024: «we fake being idle every once in a while»; yo lo conté en [Googlebot finge inactividad para renderizar](https://sanchezdonate.net/articulo/noticia/googlebot-iteratorect-rendering/)). Lo que cuelgues de un evento de inactividad puede ejecutarse; lo que cuelgues de un clic, no.

Para el contenido plegable hay una alternativa nativa que no depende de JavaScript: `details` y `summary`. El texto está en el HTML desde el principio, el navegador se encarga de abrirlo y cerrarlo, y funciona en todos los navegadores:

```
<details>
  <summary>Pregunta que ve el usuario</summary>
  <p>Respuesta que ya está en el HTML aunque esté plegada.</p>
</details>
```

De esta misma regla sale lo de los muros de pago. Si envías el artículo completo desde el servidor y lo ocultas con JavaScript hasta que el usuario demuestra que está suscrito, el contenido está en el DOM y Google lo ve, igual que cualquiera que desactive JavaScript. [La documentación de Google](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript) lo advierte: no es una forma fiable de limitar el acceso. El contenido completo solo debería salir del servidor cuando la suscripción esté confirmada.

### Googlebot no hace scroll: agranda la ventana

**Googlebot no hace scroll ni dispara eventos de scroll.** Lo dice Google y se ha comprobado desde dentro de la página escuchando ese evento: nunca llega.

¿Cómo ve entonces lo de abajo? Agrandando la ventana. Google lo llama viewport expansion (lo explicaron en [el episodio 96 de Search Off the Record](https://www.youtube.com/watch?v=l2E3gBDP_qY&t=1442s)): el renderizador va estirando la ventana, sobre todo en alto, para que se dispare lo que solo ocurre cuando un elemento entra en pantalla. Por eso Google lee lo que queda por debajo del primer pantallazo sin haber bajado nunca.

¿Cuánto de alta es esa ventana? Hay tres cifras que se suelen mezclar, y el primero en mezclarlas fui yo, cuando escribí que Google renderiza «con exactamente 1750px de alto»:

| Medida | Cifra | Quién y cómo | Qué significa |
| --- | --- | --- | --- |
| Ventana inicial del Googlebot móvil | 981 × 1742 px (`innerWidth` × `innerHeight`) | Tony McCreath, leyéndolo con JavaScript desde la propia página (Chrome 131) | El alto con el que se calcula el primer layout en su página de prueba |
| Corte de la captura de las herramientas | 1750 px en el visor de Search Console; 10.000 px en la prueba de resultados enriquecidos como ordenador | Yo, a propuesta de Mario Mangas (junio de 2025) | Hasta dónde llega la imagen que te enseñan, no lo que Google procesa |
| Alto tras el redimensionado | 1.251.074 px | Tony McCreath: dos eventos `resize`, hacia los 5 s y los 5,3 s del reloj interno | Hasta dónde se estiró la ventana en una página que él hacía crecer sin parar |

La ventana inicial depende de la escala que declara el navegador, así que con otra configuración de viewport el número podría ser otro. El alto final sale de una página que su autor hacía crecer sin parar, y Google no ha publicado ninguna cifra. La captura es un límite de la herramienta, y está explicada en [el capítulo de las herramientas](#corte-1750). Lo que comparten las tres es la idea: ventana muy alta y nada de scroll.

### Carga diferida y scroll infinito: por visibilidad sí, por scroll no

De lo anterior sale una regla para la carga diferida. Funciona la que se dispara cuando un elemento se vuelve visible, porque al estirar la ventana todo acaba siendo visible. No funciona la que escucha el evento `scroll`, porque ese evento no llega nunca.

Esto, por ejemplo, Google no lo va a ejecutar:

```
// Carga más productos al bajar: depende del evento scroll
window.addEventListener('scroll', () => {
  if (window.scrollY + window.innerHeight > document.body.offsetHeight - 300) {
    cargarMasProductos();
  }
});
```

Esto sí, porque se basa en visibilidad:

```
<!-- Imágenes e iframes: carga diferida nativa -->
<img src="/img/producto.webp" loading="lazy" width="600" height="400" alt="…">

<!-- Contenido: un elemento centinela al final de la lista -->
<div id="centinela"></div>
<script>
  new IntersectionObserver((entradas) => {
    if (entradas[0].isIntersecting) cargarMasProductos();
  }).observe(document.getElementById('centinela'));
</script>
```

Es lo que pide [la documentación de Google sobre carga diferida](https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading): cargar el contenido cuando es visible en el viewport y no depender de acciones del usuario.

Aun así, en un listado con scroll infinito no cuentes con que Google cargue más de unas pocas tandas: en una prueba publicada hubo dos redimensionados de la ventana y el DOM acabó con tres «páginas». Lo seguro es que cada tanda tenga su URL propia y persistente (`?page=2`), que esas URL estén enlazadas entre sí con enlaces normales y que la URL se actualice con la History API al avanzar.

Con las imágenes, lo que se comprueba es que la URL acaba en el atributo `src` del `<img>` en el HTML renderizado. Durante el render, el WRS ni siquiera descarga imágenes ni vídeos: le basta con saber dónde están.

### La pesadilla del 100vh: un hero sin max-height

Un problema de renderizado no siempre viene de JavaScript. A veces viene de una sola línea de CSS. `vh` es viewport height: un elemento con `100vh` mide el 100 % del alto de la ventana. Es muy habitual dárselo al primer bloque de la página para que el hero ocupe la pantalla entera:

```
.fullheight {
  height: 100vh;
}
```

En tu navegador queda bien. En Googlebot la ventana ya arranca muy alta y después crece, y el hero crece con ella hasta que el renderizador se rinde y deja de estirar. Puedes tener todo el contenido en el HTML, sin depender de JS para nada, y aun así enseñarle a Google una página que parece vacía. Este es el aspecto que tiene en «Ver página probada» de Search Console:

![Captura de Search Console de una web con el primer bloque a 100vh: el fondo del hero estirado ocupa casi toda la imagen y no se ve texto](https://seomanal.es/uploads/articulos/2-a6b2134f08999018.webp)
*Un hero con 100vh en la captura de Search Console.*

No hay texto a la vista: solo el fondo del hero, estirado de forma desproporcionada. Le pasaba también a la página de Pandora cuando la pasé por la prueba de resultados enriquecidos:

![Página de Pandora en la prueba de resultados enriquecidos: el hero a 100vh ocupa la captura y tapa el contenido principal](https://seomanal.es/uploads/articulos/2-a5f018d3835b7149.webp)
*La página de Pandora en la prueba de resultados enriquecidos.*

Con `dvh` (la unidad de alto «dinámica», que se ajusta cuando aparecen y desaparecen las barras del navegador móvil) ocurre lo mismo si no hay un `max-height`.

#### ¿Afecta a la indexación o solo a la vista previa?

Que la captura sale deformada no lo discute nadie. Lo que se discute es si además perjudica la indexación o el posicionamiento:

- **Mi experiencia.** He visto bastantes webs con este problema mejorar de forma casi instantánea al corregirlo, y sostengo que puede acabar en thin content e incluso en [soft 404](https://sanchezdonate.net/articulo/soft-not-found-google/) (lo conté en [El VH de CSS afecta a la renderización](https://sanchezdonate.net/articulo/vh-css-rendering/)), en «Rastreada: actualmente sin indexar» y en productos o listados que dejan de ser relevantes. Es experiencia de consultoría, sin un caso publicado, y sobre el papel no debería afectar.
- **Lo que dice Google.** Preguntado por esto mismo en [Search Off the Record](https://www.youtube.com/watch?v=l2E3gBDP_qY&t=1506s), la respuesta fue que tendría que mirarlo, porque ha habido algún cambio, pero que no cree que sea necesariamente un problema, ya que el texto sigue en el DOM. Aun así, recomiendan poner un alto máximo.

Hay un argumento a favor de que sí pese: Google no indexa solo el DOM, también la geometría del árbol de renderizado, el tamaño y la posición de cada cosa. Si el layout coloca tu texto a miles de píxeles por debajo de un bloque vacío, es plausible que eso cuente aunque el texto esté en el DOM. En marcas consolidadas la autoridad lo compensa; en un proyecto normal yo no contaría con ello. Me falta publicar un caso con datos de antes y después, y lo añadiré aquí si consigo permiso para enseñarlo.

#### La solución: max-height

En el remedio coincide todo el mundo, y es una línea de CSS:

```
.fullheight {
  height: 100vh;
  max-height: 800px;
}
```

Los 800 px son un ejemplo; pon un máximo razonable para tu diseño. Si lo prefieres, puedes aplicarlo solo en pantallas estrechas:

```
.fullheight {
  height: 100vh;
}
@media only screen and (max-width: 800px) {
  .fullheight {
    max-height: 800px;
  }
}
```

El hero queda con un límite de altura sin perjudicar el diseño, y no necesitas resolver el debate para actuar.

![Captura de Search Console de la misma web tras añadir max-height al hero: el texto ya aparece](https://seomanal.es/uploads/articulos/2-62860ef6826bfce6.webp)
*La misma web después de añadir el max-height. La captura está censurada por privacidad, pero se aprecia que el texto ya es visible.*

Para comprobarlo, pasa la URL por la prueba en vivo de Search Console, o por la prueba de resultados enriquecidos si la web no es tuya, y mira la captura. Para este problema la captura sí sirve, porque es justo donde se ve el hero estirado. Pero es solo el síntoma: lo que cuenta es que el contenido esté en el HTML renderizado.

### Pseudoelementos: texto que se ve y no cuenta

El [CSS](https://sanchezdonate.net/articulo/css-posicionamiento-web/) también se puede usar al revés: para enseñar un texto al usuario sin que cuente como texto. Lo que insertas con `::before` o `::after` se ve en pantalla, pero no está en el DOM, así que el renderizado no lo recoge. Tampoco sale en los fragmentos de los resultados, sin necesidad de un [data-nosnippet](https://sanchezdonate.net/articulo/data-nosnippet/).

```
.etiqueta::before {
  content: "#";
}
```

Lo comprobaron en directo en [Search Off the Record](https://www.youtube.com/watch?v=l2E3gBDP_qY&t=1322s), y contaron el caso de una web que pintaba así la almohadilla de sus hashtags: para Google eran palabras sin más. Para lo decorativo (un icono, una coletilla que no quieres que Google lea) es perfecto. Para contenido que quieres posicionar, no lo uses.

### Qué no soporta el WRS

El WRS es un Chromium al día, pero le faltan cosas que tiene el navegador de cualquier usuario. [Google documenta](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript) lo que no hace:

- Rechaza las solicitudes de permisos (cámara, ubicación, notificaciones). Si el contenido solo aparece después de aceptar, Google no lo ve.
- Solo habla HTTP. No soporta WebSockets ni WebRTC, así que lo que llegue por ahí necesita una alternativa por HTTP.
- No soporta WebGL. Lo que pintes con WebGL, o se omite para Googlebot o se prerrenderiza en servidor.
- No guarda nada entre cargas: Local Storage, Session Storage y cookies se borran de una página a la siguiente.
- No resuelve de forma fiable los fragmentos usados como rutas (`#/productos`). Para cambiar de vista, History API.
- Aplana el shadow DOM y el light DOM. Un web component que no use `slot` puede no mostrar en el HTML renderizado lo que esperas.

A esa lista yo añado los [web workers](#web-workers): en la única prueba publicada, lo que dependía de ellos casi nunca llegó al HTML renderizado.

### Recursos bloqueados: el error más simple y más repetido

El error más común con JavaScript sigue siendo pasarse con el robots.txt y bloquear ficheros que hacen falta para renderizar. Google no renderiza JavaScript de ficheros bloqueados, y si el WRS no puede obtener un recurso crítico, puede tener problemas para extraer el contenido y para que la página posicione.

La API de la que tira tu JavaScript cuenta igual que el JS y el CSS: si el endpoint está bloqueado, el script se ejecuta pero el contenido no llega. Este robots.txt, que parece prudente, deja una tienda en CSR sin productos para Google:

```
User-agent: *
Disallow: /api/
Disallow: /assets/js/
```

No todo lo que Google deja de pedir es un problema. Su navegador cancela por su cuenta lo que no le sirve: fuentes, medios, URL que dan 404 y los eventos de analítica. Ver recursos sin cargar en la inspección de URL no es grave por sí mismo. Lo grave es que entre ellos esté algo de lo que depende tu contenido.

### Caché de hasta 30 días, ficheros con huella y CDN

El WRS intenta cachear todos los JS y CSS de las páginas que renderiza y los guarda **hasta 30 días**, sin hacer caso a tus cabeceras de caché (lo explicaron en [Crawling December](https://developers.google.com/search/blog/2024/12/crawling-december-resources)). Para el crawl budget es una buena noticia: no hay que preocuparse en exceso por lo que cuesta renderizar. Pero tiene su cara B: si cambias el contenido de un fichero sin cambiarle el nombre, Google puede seguir renderizando con la versión antigua durante semanas.

La solución es meter una huella del contenido en el nombre del fichero, como hacen casi todos los empaquetadores:

```
<!-- Mal: el nombre no cambia aunque cambie el fichero -->
<script src="/js/main.js"></script>

<!-- Bien: la huella cambia solo cuando cambia el contenido -->
<script src="/js/main.2bb85551.js"></script>
```

Cuando cambia el fichero, cambia la URL y la caché de Google se invalida sola. Cuidado con el extremo contrario: un parámetro que cambia en cada deploy o en cada petición (`main.js?v=1717171717`) obliga a Google a volver a pedirlo todo aunque nada haya cambiado, y eso es crawl budget tirado. La huella tiene que salir del contenido del fichero.

Sobre el CDN: Google llegó a recomendar alojar los recursos en otro hostname para trasladar allí el consumo de crawl budget, y lo matizó a los pocos días. La conexión extra a otro dominio puede ralentizar la página, así que no lo recomienda para el JS y el CSS críticos y sí para ficheros grandes no críticos, como vídeos o descargas.

### Enlaces, botones y redirecciones por JavaScript

Un enlace, para Google, es un `<a>` con `href`. Inyectarlo con JavaScript no es problema, siempre que sea un enlace de verdad:

```
<!-- Google lo sigue -->
<a href="/zapatillas/running/">Running</a>

<!-- Google no lo sigue: no son enlaces -->
<span onclick="location.href='/zapatillas/running/'">Running</span>
<button onclick="irA('running')">Running</button>
<a href="javascript:irA('running')">Running</a>
```

Lo que sí puede pasar es que Google encuentre algo con aspecto de URL en el código, aunque no esté en un enlace, y lo mande a rastrear: [Vercel y MERJ](https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process) vieron URL descubiertas dentro de un JSON y, en su prueba, que la URL estuviera en un enlace o en un JSON no cambió la prioridad con la que Google la rastreó. Así que no fíes tu enlazado interno a que Google encuentre cadenas sueltas, y no des por hecho que una URL escrita dentro de un script no se va a rastrear.

Las redirecciones por JavaScript funcionan: el WRS las sigue como un navegador normal, y la propia Google las usó al migrar su blog desde Blogger. Pero tardan más, porque una redirección HTTP se resuelve en el rastreo y la de JavaScript tiene que esperar al renderizado. Si puedes elegir, redirección de servidor.

Qué pasa cuando el JavaScript toca el noindex, la canonical o los códigos de estado es el tema del siguiente capítulo.

## El HTML inicial manda: noindex, canonical, códigos de estado y soft 404

Antes de renderizar nada, Google ya ha decidido cosas con el HTML que sale de tu servidor. El DOM final sigue contando, pero hay señales que se leen en el rastreo, como el código de estado, el meta robots y la canonical, y si ahí dicen «no», lo que haga después tu JavaScript llega tarde o no llega.

La regla de todo este capítulo es una: **el HTML inicial tiene que ser ya indexable**. Lo demás son matices, y alguno ha cambiado en el último año.

### Qué mira Google antes de mandar la página a renderizar

Googlebot guarda la respuesta HTTP con sus cabeceras y se la pasa al sistema de indexación. Lo primero que se mira es el código de estado: los 3xx se resuelven en el propio rastreo, los 4xx abortan la indexación (salvo el 429, que Google [trata como un error de servidor](https://developers.google.com/search/docs/crawling-indexing/http-network-errors)) y los 5xx se tratan como un fallo temporal hasta el siguiente rastreo. Con un 200 todavía se comprueba si la página es un error encubierto, el soft 404 de toda la vida. También se evalúan el noindex y la canonical. El renderizado viene después.

### Noindex en origen: de «no se renderiza» a «puede no renderizarse»

Durante años la regla fue categórica: Google no renderiza páginas noindex. Era lo documentado y lo que se medía: con noindex en el HTML inicial, la página no se renderizaba y el JavaScript que debía quitarlo ni llegaba a ejecutarse. Tiene lógica, porque renderizar es caro y una página que no se va a indexar no lo necesita.

En 2025 cambió la foto. Dave Smart publicó [Noindex No Longer Means Not Rendered](https://tamethebots.com/blog-n-bits/noindex-does-not-mean-not-rendered) con cuatro páginas de prueba, cada una con un `fetch` a un endpoint que registra quién lo pide:

| Página de prueba | ¿Googlebot ejecutó el fetch? | Estado en Search Console | Último rastreo |
| --- | --- | --- | --- |
| Meta robots noindex que el propio JavaScript elimina | Sí, por POST | Excluida por noindex | 20 de mayo de 2025 |
| Cabecera X-Robots-Tag: noindex | Sí, por POST | Excluida por noindex | 22 de junio de 2025 |
| Código de estado 404 | No | No encontrada (404) | 22 de junio de 2025 |
| Meta robots noindex con redirección por JavaScript | Sí, por POST | Excluida por noindex (el destino quedó como URL desconocida para Google) | 22 de junio de 2025 |

En las tres páginas con noindex Googlebot ejecutó el JavaScript, y las tres siguieron excluidas. La petición llegó por POST, lo que apunta a que venía del renderizado y no de que Google viera algo con pinta de URL en el HTML y lo pidiera por si acaso. Es un sitio de pruebas, no se sabe si es un fallo o algo intencionado y puede revertirse.

Un año antes, Vercel y MERJ habían medido lo contrario. En [su estudio de 2024](https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process), las páginas con noindex en el HTML inicial no se renderizaron, tuvieran el JavaScript que tuvieran, y el script que quitaba el noindex ni llegó a ejecutarse. Las dos observaciones pueden ser ciertas a la vez: algo cambió entre abril de 2024 y mayo de 2025, o depende del sitio. Lo que no vale es quedarse con una y callar la otra.

En diciembre de 2025 Google cambió su documentación, y hoy dice que al encontrar un noindex «puede» saltarse el renderizado y la ejecución de JavaScript. En la misma página sigue habiendo otra frase que dice que las páginas con noindex no se encolan para renderizar, así que la propia documentación no es del todo coherente. Con noindex en el HTML inicial, Google **puede** no renderizar la página. No des por hecho ni una cosa ni la contraria.

### Quitar un noindex con JavaScript no funciona; ponerlo, sí

Lo que no ha cambiado: un noindex en origen no se quita con JavaScript. En la prueba anterior el script que eliminaba el noindex llegó a ejecutarse, y la página siguió excluida. Y si el noindex viene en la cabecera `X-Robots-Tag`, ni siquiera hay debate: JavaScript no puede reescribir una cabecera HTTP.

Al revés sí funciona. Si el HTML inicial es indexable y el JavaScript añade un noindex, la página acaba fuera del índice. Por eso un noindex se puede implementar desde Google Tag Manager.

Eso sí, no siempre es inmediato: una página con mucho contenido de calidad puede indexarse primero con el HTML del servidor, sin el noindex, y no salir del índice hasta que se procesa la versión renderizada. Esa transición puede durar de unas horas a unos días. Cuanto más claras sean las señales desde el servidor, menos dependes de ese baile.

### El flashazo de noindex: cuando el noindex se cuela a mitad de la carga

Hay un caso raro que encaja aquí y al que en su día puse nombre. El síntoma: en Search Console la página sale como excluida por un meta noindex, le das a «Probar URL publicada» y está en index. Compruebas el código fuente y el DOM, y está en index. ¿Qué ha podido pasar?

> Un flashazo de noindex es un noindex que aparece un instante en el DOM mientras la página se renderiza y luego desaparece.

Puede pasar sobre todo en webs hechas con frameworks de JavaScript: el servidor no manda ningún meta robots, el framework acaba poniéndolo en index y, por el camino, se cuela un noindex. Que Google se quede justo con ese noindex momentáneo es **una hipótesis mía**: no lo dice ninguna documentación, no tengo cifras y no he publicado un caso real. Me parece plausible porque lo que cuenta es el DOM en el momento del snapshot y un noindex puesto por JavaScript sí funciona; lo que nadie ha probado es que Google llegue a capturar ese estado intermedio.

Lo que sí es seguro es el remedio: si el meta robots correcto sale ya desde el servidor, el riesgo desaparece de raíz.

Antes de culpar al flashazo, descarta otras dos explicaciones del mismo síntoma:

- Que la versión indexada sea de un rastreo anterior a un cambio tuyo. Mira la fecha del último rastreo en la inspección de URL: la inspección enseña lo que Google vio, y la prueba en vivo lo que vería ahora.
- Que el Googlebot real haya renderizado con un JavaScript antiguo de [su caché](#cache-30-dias), que la prueba en vivo no usa.

Descartado eso, el método que publiqué en [Revisar flashazos de noindex](https://sanchezdonate.net/articulo/curiosidades/revisar-flashazos-noindex/). [Abre DevTools](https://sanchezdonate.net/articulo/devtools-para-seo/), ve a Fuentes > Fragmentos y guarda este script como fragmento nuevo:

![Panel Fuentes de DevTools con la pestaña Fragmentos abierta y las primeras líneas del script detector pegadas en un fragmento nuevo](https://seomanal.es/uploads/articulos/2-335ba628b48d4c2d.webp)
*DevTools en Fuentes > Fragmentos, con el script guardado como fragmento.*

```
(() => {
  console.clear();
  const log = (etiqueta, msg, color = 'inherit') =>
    console.log(`%c${etiqueta} ${msg}`, `color:${color}; font-weight:bold;`);
  let metaObserver = null;
  let canonicalObserver = null;
  let headObserver = null;

  // Observer para meta robots
  const startMetaObserver = (meta) => {
    log('[VISTO]', `Detectado <meta name="robots"> con contenido inicial: ${meta.getAttribute('content')}`, '#aaf');
    if (metaObserver) metaObserver.disconnect();
    metaObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'content') {
          const oldValue = mutation.oldValue;
          const newValue = meta.getAttribute('content');
          const time = new Date().toLocaleTimeString();
          log('[CAMBIO]', `[${time}] META robots cambiado`, '#ffcc00');
          console.log(' ▸ Anterior:', oldValue);
          console.log(' ▸ Nuevo:', newValue);
          console.trace(' Origen del cambio');
        }
      }
    });
    metaObserver.observe(meta, {
      attributes: true,
      attributeFilter: ['content'],
      attributeOldValue: true
    });
    log('[OK]', 'Observando cambios en el contenido del meta robots', '#0f0');
  };

  // Observer para canonical
  const startCanonicalObserver = (canonical) => {
    log('[VISTO]', `Detectado <link rel="canonical"> con href inicial: ${canonical.getAttribute('href')}`, '#aaf');
    if (canonicalObserver) canonicalObserver.disconnect();
    canonicalObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'href') {
          const oldValue = mutation.oldValue;
          const newValue = canonical.getAttribute('href');
          const time = new Date().toLocaleTimeString();
          log('[CAMBIO]', `[${time}] CANONICAL cambiado`, '#ff69b4');
          console.log(' ▸ Anterior:', oldValue);
          console.log(' ▸ Nuevo:', newValue);
          console.trace(' Origen del cambio');
        }
      }
    });
    canonicalObserver.observe(canonical, {
      attributes: true,
      attributeFilter: ['href'],
      attributeOldValue: true
    });
    log('[OK]', 'Observando cambios en el href del canonical', '#0f0');
  };

  // Esperar a que aparezcan ambos elementos
  const waitForElements = () => {
    const existingMeta = document.querySelector('meta[name="robots"]');
    const existingCanonical = document.querySelector('link[rel="canonical"]');
    if (existingMeta) {
      startMetaObserver(existingMeta);
    }
    if (existingCanonical) {
      startCanonicalObserver(existingCanonical);
    }
    if (existingMeta && existingCanonical) {
      return;
    }
    if (!existingMeta) {
      log('[ESPERA]', 'Esperando a que aparezca <meta name="robots">...', '#999');
    }
    if (!existingCanonical) {
      log('[ESPERA]', 'Esperando a que aparezca <link rel="canonical">...', '#999');
    }
    headObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node.tagName === 'META' && node.getAttribute('name') === 'robots' && !metaObserver) {
            log('[OK]', `Detectado nuevo <meta name="robots"> añadido al DOM: ${node.getAttribute('content')}`, '#0f0');
            startMetaObserver(node);
          }
          if (node.tagName === 'LINK' && node.getAttribute('rel') === 'canonical' && !canonicalObserver) {
            log('[OK]', `Detectado nuevo <link rel="canonical"> añadido al DOM: ${node.getAttribute('href')}`, '#0f0');
            startCanonicalObserver(node);
          }
        }
      }
      // Si ambos observadores están activos, desconectar el headObserver
      if (metaObserver && canonicalObserver) {
        headObserver.disconnect();
      }
    });
    headObserver.observe(document.head, { childList: true, subtree: true });
  };

  waitForElements();
})();
```

Es una función que se ejecuta sola, limpia la consola y crea tres `MutationObserver`: uno vigila el `content` del meta robots, otro el `href` de la canonical y el tercero espera a que esos dos elementos aparezcan en el `head` si aún no existen. Por cada cambio escribe la hora, el valor anterior, el nuevo y un `console.trace` con el script que lo ha provocado.

Una vez guardado, ve a la pestaña Red y limita la velocidad a 3G, para que te dé tiempo a lanzar el fragmento antes de que el JavaScript de la página toque el head.

![Pestaña Red de DevTools con la limitación de velocidad en 3G y la casilla Inhabilitar caché marcada](https://seomanal.es/uploads/articulos/2-81d3f12f33bfce90.webp)
*Pestaña Red con la velocidad limitada a 3G. En la captura también está marcada «Inhabilitar caché».*

Vuelve a Fuentes > Fragmentos, recarga la página y dale a «Ejecutar» cuanto antes:

![Menú contextual de un fragmento de DevTools con la opción Ejecutar en primer lugar](https://seomanal.es/uploads/articulos/2-1aa319f648aa3210.webp)
*Botón derecho sobre el fragmento y «Ejecutar», cuanto antes.*

Después devuelve la red a su velocidad normal y lee la consola:

![Consola de DevTools con la salida del detector: el meta robots no existía, se añade al DOM como INDEX, FOLLOW y queda en observación](https://seomanal.es/uploads/articulos/2-e40e698cf107e84e.webp)
*Salida del detector en un caso limpio: el meta robots se añade al DOM ya como INDEX, FOLLOW.*

La captura es de un caso limpio, con una versión anterior del script que solo vigilaba el meta robots: el meta no existía al inicio, se añade directamente como INDEX, FOLLOW y no cambia después. Un flashazo se vería con una línea «META robots cambiado», el valor anterior, el nuevo y la traza con el script culpable.

El script tiene límites:

- Solo vigila el primer meta robots y la primera canonical que encuentra.
- Si el framework elimina la etiqueta y la vuelve a insertar, añade un segundo meta robots, usa `meta name="googlebot"` o la mete fuera del head, no lo registra.
- Todo cambio anterior a la ejecución del fragmento se pierde. De ahí el 3G y la prisa.

Si sabes programarlo mejor y cubrir esos casos, adelante.

### Páginas noindex en CSR: enlaces, facetas y cuándo compensa robots.txt

Si una paginación lleva noindex en una web en CSR, ¿existen para Google los enlaces que genera su JavaScript? Antes habría dicho que no. Hoy diría «no cuentes con ellos». Que Googlebot ejecute JavaScript en una página noindex no significa que descubra ni siga los enlaces que ese JavaScript genera: en la prueba de antes, la redirección por JavaScript de la página noindex no se siguió, aunque quizá Google simplemente no quiso probarla. Y sigue valiendo lo que ya sabíamos: una página noindex, a largo plazo, [acaba tratándose como nofollow](https://www.youtube.com/watch?v=9GNg8R-X8LQ&t=3291s). Lo dijo John Mueller en un hangout del 15 de diciembre de 2017 y no está en la documentación.

![Documentación de Google sobre paginación: recomienda enlazar las páginas en secuencia y dice que Google intenta reconocerlas e indexarlas](https://seomanal.es/uploads/articulos/2-5c89fe1c51ebaa9c.webp)
*La documentación de Google sobre paginación habla de reconocer e indexar las páginas de una secuencia.*

La documentación tampoco ayuda a decidir: por un lado explica cómo reconoce e indexa las paginaciones; por otro [recomienda no indexar las variaciones de ordenación](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading?hl=es#avoid-indexing-variations). Ese debate sigue abierto.

Y una consecuencia práctica: **el noindex no te garantiza ahorrar renderizado ni llamadas a API**. En un sitio grande, los buscadores internos y la navegación facetada con noindex pueden seguir disparando peticiones pesadas a tus endpoints. Ahí puede compensar el robots.txt, porque si bloquea la URL, Googlebot ni la pide y no renderiza nada.

Pero no son intercambiables. Si Googlebot no pide la URL, tampoco lee el noindex que lleve dentro, y bloquear no es desindexar: cuando lo comprobé, amstel.es posicionaba primera por su marca estando bloqueada por robots.txt. Noindex cuando quieres la página fuera del índice; robots.txt cuando lo que quieres es que Google no gaste recursos en ella.

### La canonical, en el HTML inicial

En lo importante Google y yo coincidimos: la canonical tiene que ser correcta ya en el HTML inicial, y correcta quiere decir propia (self-canonical) o ausente. La documentación dice que la mejor forma de fijarla es en el HTML; si no puedes, admite que la pongas con JavaScript dejándola fuera del HTML inicial. Lo que pide es no usar JavaScript para cambiarla por otra distinta de la que venía y, si la inyectas, que sea la única de la página.

Donde no coincidimos es en la consecuencia. Mi experiencia es que, si el HTML inicial trae una canonical hacia otra URL, Google considera la página no indexable y no la lleva a renderizar, aunque luego el JavaScript la corrija. El fallo típico es una canonical hacia la home en la parte estática que después el framework cambia en el DOM: de inicio solo sería indexable la home.

Google no lo documenta así. Como motivos para saltarse el renderizado solo menciona el noindex y los códigos distintos de 200, y de las canonical en conflicto solo dice que pueden dar resultados inesperados. Lo mío es experiencia de consultoría, sin un experimento publicado; lo de Google, que el resultado no está garantizado. El consejo es el mismo con las dos versiones: no confíes en arreglar la canonical con JavaScript. Para cazar un framework que la cambia, el fragmento del flashazo también vigila la canonical y te dice qué script la toca.

### Códigos de estado y soft 404

Lo que no devuelve un 200 puede quedarse sin renderizar. Google lo dice en condicional y las mediciones van en la misma línea: los 304 se renderizaban con el contenido del 200 original, pero el resto de 3xx, los 4xx y los 5xx no, y en la prueba del noindex la página con 404 ni ejecutó el `fetch`. Un contenido que solo aparece por JavaScript en una URL que responde 404 no existe para Google.

En una SPA (aplicación de una sola página) con enrutado en cliente pasa lo contrario: el servidor responde 200 a todo y el error lo pinta JavaScript. Eso es un soft 404 de manual. [Google da dos salidas](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript), y puedes usar una o las dos:

```
// Opción 1: mandar al usuario a una URL que el servidor responde con un 404 real
if (!producto) {
  window.location.href = '/no-encontrado/';
}

// Opción 2: marcar la página de error con un noindex
if (!producto) {
  const meta = document.createElement('meta');
  meta.name = 'robots';
  meta.content = 'noindex';
  document.head.appendChild(meta);
}
```

La segunda es la regla de antes aplicada a propósito: poner un noindex con JavaScript funciona.

Un consejo que es mío, no de Google: evita la palabra «404» en el title y en el h1, aunque hables de los 404. Es posible que la página se detecte automáticamente como soft 404, y esa comprobación puede hacerse ya con el HTML inicial, antes de renderizar. Google no documenta ese disparador.

### Un head limpio

En el `<head>` solo caben etiquetas válidas para el head. Si metes una que no lo es, cualquier navegador, incluido el Chromium de Google, cierra el head en ese punto, y lo que venga detrás queda ya en el body:

```
<head>
  <title>Zapatillas de running</title>
  <div id="banner-cookies"></div>   <!-- aquí se cierra el head -->
  <meta name="robots" content="noindex">   <!-- ya está en el body -->
  <link rel="canonical" href="https://ejemplo.com/running/">
</head>
```

Si detrás venían el meta robots, la canonical o los hreflang, has dejado tus señales más importantes donde no toca. El sospechoso habitual es un script de terceros que inyecta un `div` o un `iframe` en el head.

### Cómo auditarlo en un minuto

Todo lo de este capítulo se comprueba pidiendo la URL sin ejecutar JavaScript. Con `curl`, desde la terminal:

```
# Código de estado
curl -s -o /dev/null -w "%{http_code}\n" https://ejemplo.com/pagina/

# Cabecera X-Robots-Tag (con GET: un HEAD puede responder con otras cabeceras)
curl -s -D - -o /dev/null https://ejemplo.com/pagina/ | grep -i x-robots-tag

# Meta robots y canonical del HTML inicial (solo las etiquetas, aunque el HTML venga en una sola línea)
curl -s https://ejemplo.com/pagina/ | grep -ioE '<(meta|link)[^>]*(robots|googlebot|canonical)[^>]*>'
```

Después compara lo que sale con el HTML de la página rastreada en la inspección de URL de Search Console. Si el servidor dice una cosa y el HTML rastreado otra, ya sabes dónde mirar.

## El misterio de los 5 segundos: cuánto tarda y cuánto espera Google

«Google renderiza en 5 segundos.» Lo habrás leído mil veces, y yo mismo lo escribí: en la [guía avanzada sobre el renderizado](https://sanchezdonate.net/articulo/guia-avanzada-sobre-el-renderizado/) llegué a poner, con mayúsculas, que todo lo que tardase más de 5 segundos en aparecer por JavaScript era invisible para Google. El experimento que había detrás sigue dando el mismo resultado. Lo que he tenido que revisar es qué significa ese 5.

El lío viene de que la frase mezcla dos preguntas distintas:

1. **¿Cuánto tarda Google en renderizar tu página desde que la rastrea?** Es la espera en la cola de renderizado. De aquí sale el primer «5 segundos»: una mediana.
2. **¿Cuánto deja correr tu página antes de capturarla?** Es lo que pasa dentro del navegador de Google. De aquí sale el segundo «5 segundos», el de los contadores, y es el misterio de verdad.

Y hay un tercer reloj que se cuela en las dos: el tiempo hasta indexar, desde que publicas hasta que la página sale en los resultados. Ahí entran el descubrimiento de la URL, la cola de rastreo, la canonicalización y la calidad del contenido, además del render. Desde fuera solo vemos dos momentos, la petición en tus logs y la aparición en Google, y lo de en medio lo deducimos.

### Primera pregunta: cuánto tarda Google en renderizar

Entre que Google rastrea una página y la renderiza pasan, en la inmensa mayoría de los casos, segundos o minutos. Estas son las cifras publicadas:

| Fuente | Qué mide | Cifra |
| --- | --- | --- |
| Google, [Chrome Dev Summit 2018](https://www.youtube.com/watch?v=YP2tMBhyU0I&t=299s) (Tom Greenaway y Martin Splitt) | Del rastreo al render | «Minutos, una hora, días o hasta una semana». Lo retiraron un año después |
| Google, [Chrome Dev Summit 2019](https://www.youtube.com/watch?v=4pOH8Smd0Xs&t=463s) | Del rastreo a tener la página renderizada | Mediana de 5 segundos. Percentil 90, «en minutos» |
| Google, [SEO on the Beach 2023](https://www.youtube.com/watch?v=BU8ZvTq4A6M&t=1110s) | Hasta tener la página renderizada | Mediana de 5 segundos; el 10 % más lento, unos pocos minutos |
| Google, [entrevista de 2024](https://www.youtube.com/watch?v=0iHDk7uqByI&t=1221s) | Del rastreo al render | Minutos para la mayoría de las páginas, a veces horas y muy rara vez más |
| Google, [Andalu-SEO 2026](https://andalu-seo.es/ponente/2026/martin-splitt) | Cola de renderizado frente a cola de rastreo, con sus logs | El 90 % de las URL pasa solo unos minutos en la cola de renderizado. Las horas o días se pierden en la de rastreo |
| [Documentación de Google](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) | Espera en la cola de renderizado | Unos segundos, aunque puede tardar más. No da máximo |
| [Vercel y MERJ](https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process) (2024) | De la petición del HTML al fin del render, en nextjs.org | Percentil 25: 4 s. Mediana: 10 s. Percentil 75: 26 s. Percentil 90: unas 3 h. Percentil 99: unas 18 h |
| [Tony McCreath](https://bigcommerce.websiteadvantage.com.au/tonys-theory-of-googlebot-relativity/) (2025) | De servir el HTML a la primera petición de un recurso del render | Unos 23,6 segundos en un caso, no una media |

Desde 2019 las fuentes coinciden en lo esencial: **casi todas las páginas se renderizan en segundos o minutos**, y las medianas publicadas están entre 5 y 10 segundos. Ese primer «5 segundos» es una espera en cola, y además una mediana: la mitad de las páginas tarda más.

Donde las fuentes dejan de coincidir es en el 10 % más lento: minutos según Google, horas según la única medición externa amplia. Esa medición tiene pistas de por qué. En las URL con parámetros el percentil 75 pasaba de 22 segundos a 31 minutos, y las secciones que cambian poco se renderizaban más tarde que las que se actualizan a menudo. Google, por su parte, dice que esos retrasos largos existen pero no son del render: la página está rastreada y otro sistema le ha dado poca prioridad. Desde fuera es imposible separar «estaba en la cola de renderizado» de «Google decidió que esa URL podía esperar», y una URL con un `?ref=` que no cambia el contenido es la candidata perfecta a esperar. No hay una cifra cerrada para ese 10 %.

Los estudios que más se citan contra la versión de Google, los de [Onely en 2019](https://www.searchenginejournal.com/javascript-indexing-delays-google/335703/) (investigación propia de Tomek Rudzki, publicada en Search Engine Journal) y [en 2022](https://www.onely.com/blog/google-needs-9x-more-time-to-crawl-js-than-html/), miden otra cosa. El de 2019 mira cuánto contenido dependiente de JavaScript estaba indexado al cabo de un día o de un mes: es el tercer reloj, y no distingue entre «no lo renderizó» y «lo renderizó y decidió no indexarlo». El de 2022, el del titular «Google necesita nueve veces más tiempo para rastrear JavaScript», mide cuánto tarda Googlebot en seguir un enlace que solo existe después de renderizar. Ninguno de los dos mide el tiempo de render, pero el segundo deja una lección práctica que sigue en pie.

### De las «dos olas» al coste real del CSR: descubrir los enlaces

La idea de que el JavaScript se indexa «en una segunda ola» viene de 2018, cuando Google explicó que primero indexaba el HTML y el render llegaba después. En 2020 [reconocieron](https://www.youtube.com/watch?v=DK1rJhvTAdM&t=996s) que las dos olas eran una simplificación excesiva (Martin Splitt, en las office hours de JavaScript SEO del 25 de marzo de 2020: «the second wave is an oversimplification»), y hoy la documentación dice que todas las páginas con código 200 van a la cola de renderizado, tengan o no JavaScript. Aun así, el modelo sigue vivo en muchos sitios, incluido [el tutorial de Screaming Frog](https://www.screamingfrog.co.uk/seo-spider/tutorials/crawl-javascript-seo/).

Yo también escribí en 2023, en [mi artículo sobre los tipos de renderizado](https://sanchezdonate.net/articulo/renderizacion-de-javascript-en-el-seo/), que el CSR era «un proceso que necesita 3 rastreos». Lo que hay no son tres peticiones sino tres fases (rastrear el HTML, renderizarlo y volver a analizar el resultado para sacar los enlaces), y por ellas pasan todas las páginas, no solo las de CSR.

El coste real del CSR está en **el descubrimiento de enlaces**. Si los enlaces solo existen después de ejecutar JavaScript, cada nivel de profundidad exige rastrear, renderizar, volver a analizar y mandar la URL nueva a la cola de rastreo. En un nivel no se nota. En siete niveles de un sitio recién creado, el experimento de Onely tardó 313 horas en llegar al final de la cadena JavaScript, frente a 36 en la de HTML. Mediciones más cuidadosas dan una ventaja ligera para las páginas renderizadas en servidor, y un sitemap actualizado reduce mucho esa diferencia o la elimina.

La conclusión práctica: los enlaces importantes, en el HTML inicial como `<a href>` o, como mínimo, en el sitemap. Sobre todo en proyectos nuevos o poco rastreados, que es donde más pesa la cola de rastreo.

### «Rastreada: actualmente sin indexar» casi nunca es culpa del renderizado

Si le echas la culpa al renderizado de un retraso que en realidad es de rastreo o de calidad, vas a gastar semanas de desarrollo en arreglar lo que no está roto. Los sospechosos habituales de una página sin indexar son otros. Martin Splitt lo explicó en [la entrevista con Olga Zarr de mayo de 2024](https://www.youtube.com/watch?v=0iHDk7uqByI&t=595s): indexar o no depende de si el contenido es útil y de calidad, y de si Google ya tiene otro igual.

- El rastreo: la URL tarda en descubrirse o en volver a rastrearse, que es donde se pierden las horas y los días.
- Las API bloqueadas a los bots en aplicaciones que pintan en el cliente: Google renderiza, pero el contenido no llega.
- La calidad: la mayoría de las páginas sin indexar lo están porque son flojas o porque ya hay muchas que responden lo mismo.

Se comprueba en un momento. En la inspección de URL de Search Console, mira el HTML de la página rastreada (la versión indexada, no la prueba en vivo). Si tu contenido está ahí, no tienes un problema de JavaScript y toca revisar rastreo, enlazado y contenido. Si falta, entonces sí.

### Segunda pregunta: cuánto espera Google dentro de tu página

Aquí está el misterio. Para medirlo puse en el `<head>` una metaetiqueta inventada, `snapshot-counter`, que un `setInterval` incrementa cada segundo (el código está más abajo, en [el método de las tres metaetiquetas](#metodo-metaetiquetas)). Cuando Google rastrea la página, inspecciono la URL en Search Console sin darle a «Probar URL publicada» y miro qué valor tiene la metaetiqueta en el HTML de la página rastreada:

![HTML de la página rastreada en Search Console con la metaetiqueta snapshot-counter en 5](https://seomanal.es/uploads/articulos/2-ab7e7334afca2176.webp)
*Página rastreada (la versión indexada, no la prueba en vivo): `snapshot-counter` vale 5. Fíjate también en la hora que ha escrito el JavaScript, que acaba en :05. De eso hablo en [cómo medirlo](#como-medirlo).*

Marca 5. Lo he repetido en varios proyectos y siempre sale 5, en la versión indexada y en la prueba en vivo. Y con temporizadores pasa lo mismo: un texto que el JavaScript pinta a los 4 segundos aparece en el HTML de la página rastreada; uno que pinta pasados los 5, no. El contador lo publiqué en [Calcular tiempo de renderizado de Google](https://sanchezdonate.net/articulo/calcular-tiempo-de-renderizado-de-google/).

No es cosa de mi montaje. Otras dos pruebas dan lo mismo: un título que cambia cada segundo [se paró en 5](https://tamethebots.com/blog-n-bits/the-5-second-google-render-limit-myth), y en [una página que apuntaba `performance.now()` cada 10 milisegundos](https://bigcommerce.websiteadvantage.com.au/tonys-theory-of-googlebot-relativity/) el último valor fue de unos 5250, mientras un `setTimeout` de 6 segundos nunca llegó a ejecutarse.

Hasta aquí, todo encaja con «Google corta a los 5 segundos». El problema es lo que pasa con la red. La primera de esas páginas, además del contador, hacía dos peticiones encadenadas a un PHP que tardaba entre 3 y 6 segundos en responder cada vez: entre 6 y 12 segundos en total. Con un corte a los 5 segundos ese texto no podría aparecer, y apareció, en la prueba en vivo y en la indexación real. La propia página calculaba cuánto habían tardado esas llamadas, y dentro de Googlebot el resultado fue de 0,02 segundos.

La explicación es que **el navegador de Google no usa el reloj de verdad, sino uno propio** que puede acelerar, frenar o parar. Por lo que se ha medido desde dentro de la página:

- Mientras hay peticiones de red pendientes, el reloj se para. Quince recursos con medio segundo de retardo cada uno costaron casi 10 segundos reales y solo 162 milisegundos del reloj de Google.
- Con la CPU ocupada, el reloj apenas avanza.
- Cuando la página no tiene nada que hacer, el reloj se acelera: un temporizador de 5 segundos se cumplió en unos 455 milisegundos reales.
- Cuando ese reloj pasa de unos 5 segundos, Google para todo y se queda con el DOM. Por eso el temporizador de 6 segundos no llega nunca.

Google no lo ha documentado, pero lo poco que ha contado encaja. De viva voz, en un directo y en una entrevista, ha contado que [los temporizadores corren más deprisa](https://www.youtube.com/watch?v=-newLrArh6c&t=1744s) cuando la página está ociosa, que el final del renderizado no lo marca un tiempo fijo sino que [esperan a que la cola de eventos del navegador quede vacía](https://www.youtube.com/watch?v=-newLrArh6c&t=1532s), y que lo de los 5 segundos [no es cierto](https://www.youtube.com/watch?v=0iHDk7uqByI&t=1371s): «esperamos más si hace falta», siempre que confíen en que la espera va a traer contenido.

El misterio se resuelve así: **los 5 segundos existen, pero son segundos del reloj de Google, no del tuyo**. Mi contador no cronometra nada. Cuenta los ticks de un reloj que Google acelera cuando la página está quieta y congela cuando está trabajando, y llega a 5 antes del corte.

### Qué entra en la captura y qué no

Juntando todo, la regla práctica es esta (Google no la ha publicado; sale de las pruebas):

- **Un temporizador de más de unos 5 segundos no entra.** Un `setTimeout`, un `setInterval` o una animación que espera antes de pintar: el reloj de Google llega a su tope antes de que se disparen.
- **Un temporizador más corto sí entra**, y Google ni siquiera lo espera de verdad: lo adelanta.
- **Lo que tarda porque una API o un servidor son lentos puede entrar**, aunque en tu reloj pasen 9 o 10 segundos. Mientras hay una petición pendiente, el reloj de Google no corre.
- **Lo que tarda porque el JavaScript es pesado tampoco gasta ese reloj**, porque con la CPU ocupada apenas avanza.
- **Lo que depende de un clic o de un scroll no entra nunca**, tarde lo que tarde.

> Lo que dependa de un temporizador de más de unos 5 segundos, Google no lo verá. Lo que tarde por culpa de la red, sí puede verlo.

Sigo esperando que no seas malo y no aproveches lo primero para hacer una suerte de [cloaking](https://sanchezdonate.net/articulo/cloaking/). Y lo segundo tampoco invita a apurar: que Google haya esperado a una API lenta en una página de pruebas no garantiza que lo haga con la tuya.

### Pruébalo en tu web

La forma de entenderlo de verdad es verlo. Esta página de prueba junta los cuatro casos: un temporizador de 4 segundos, uno de 6, una API que tarda 8 segundos reales y un texto que solo aparece al hacer clic.

```
<p id="t4">pendiente</p>
<p id="t6">pendiente</p>
<p id="api">pendiente</p>
<p id="clic">pendiente</p>
<button id="boton">Mostrar</button>
<script>
  // 1. Temporizador de 4 segundos
  setTimeout(() => {
    document.getElementById('t4').textContent = 'temporizador-4s';
  }, 4000);

  // 2. Temporizador de 6 segundos
  setTimeout(() => {
    document.getElementById('t6').textContent = 'temporizador-6s';
  }, 6000);

  // 3. API lenta: el servidor tarda 8 segundos en responder
  fetch('/prueba-render/lenta.php', { method: 'POST' })
    .then((r) => r.text())
    .then((texto) => {
      document.getElementById('api').textContent = texto;
    });

  // 4. Solo con un clic
  document.getElementById('boton').addEventListener('click', () => {
    document.getElementById('clic').textContent = 'tras-clic';
  });
</script>
```

Y el PHP lento, en `/prueba-render/lenta.php`:

```
<?php
sleep(8); // ocho segundos de reloj real
header('Content-Type: text/plain; charset=utf-8');
header('Cache-Control: no-store');
echo 'api-lenta-8s';
```

La petición va por POST a propósito: Google cachea con ganas lo que pide por GET, y si te sirviera la respuesta guardada no habría ninguna espera de 8 segundos que medir.

Cuando Google haya rastreado la página, inspecciona la URL **sin** darle a «Probar URL publicada», abre «Ver página rastreada» y busca los cuatro `id`. Lo esperable, por lo que se ha medido:

- `t4`: «temporizador-4s».
- `t6`: «pendiente».
- `api`: «api-lenta-8s», aunque en tu reloj pasaron 8 segundos.
- `clic`: «pendiente».

Si los temporizadores te dan otra cosa, cuéntamelo, porque significaría que algo ha cambiado. Si es la API la que se queda en «pendiente», puede ser solo que esa vez Google no esperó: que lo hiciera en otras pruebas no garantiza que lo haga con la tuya. Monta la prueba en una página aparte y bórrala cuando acabes.

Tampoco esperes milagros con la red. Google sí tiene un tope en tiempo real, aunque no lo publica: Martin Splitt dijo en el [webinar de Duda de agosto de 2023](https://www.youtube.com/watch?v=-newLrArh6c&t=1831s) que existe, que es «ridiculously long» y que lo ajustan a menudo. En una prueba, una página que no paraba de pedir datos se cortó hacia los 30 segundos en la indexación real y entre los 48 y los 54 en la prueba en vivo, con un corte que su autor da por bastante variable; en otra, a los 52 segundos Google canceló las peticiones pendientes. La indexación real fue más estricta que la prueba en vivo, otro motivo para no tomar la prueba en vivo como comprobación final.

### La objeción de Google a estas pruebas

Google tiene una objeción a este tipo de experimentos, y es justo contarla. Preguntado por las páginas cuyo contenido cambia cada segundo, [contestaron](https://www.youtube.com/watch?v=0iHDk7uqByI&t=1283s) que dentro del renderizado 10 segundos pueden pasar al instante, que los temporizadores pueden dispararse desordenados, que la fecha puede ser la del día del rastreo y que no se fiarían de estas pruebas, porque un montaje raro interfiere con reglas pensadas para webs reales.

No hablaban de mi contador, pero vale igual para él. A su favor está que es reproducible: da lo mismo en tres montajes distintos. En contra, que lo que devuelve no es tiempo real. Por eso lo uso como herramienta de diagnóstico (¿se ejecutó mi JavaScript?, ¿hasta qué tick llegó?; si se queda en 0, no se ejecutó) y no como cronómetro.

### Entonces, ¿afecta la velocidad al renderizado?

Afecta, pero no porque Google «se vaya a los 5 segundos». Contenido que tardó 9 o 10 segundos reales en llegar por la red se renderizó completo. El [rendimiento web (WPO)](https://sanchezdonate.net/seo-avanzado/rendimiento-velocidad/) importa por otras razones:

- Un servidor lento reduce el rastreo: si a tu servidor le cuesta servir bytes, los rastreadores bajan el ritmo solos y cae tu frecuencia de rastreo.
- Las esperas de red muy largas sí se cortan, con los topes de arriba.
- Una página que abusa de la CPU o de la red es más frágil, en Google y en cualquier navegador.
- El usuario no espera. La paciencia del WRS es mucho mayor que la de una persona.

Y no lleves ese 5 a Screaming Frog. Su tiempo de espera AJAX viene a 5 segundos por defecto, pero es de reloj real:

![Configuración de renderizado de Screaming Frog SEO Spider con el tiempo de espera AJAX en 5 segundos y el aviso de que Googlebot es más flexible](https://seomanal.es/uploads/articulos/2-89b29d1159495e7c.webp)
*El tiempo de espera AJAX de Screaming Frog: 5 segundos por defecto, configurable de 1 a 300. El aviso de la herramienta dice que Googlebot es más flexible y que se guía por la actividad de red.*

Si tu web depende de una API que tarda más de 5 segundos, con el ajuste por defecto la herramienta puede ver menos contenido que Google. Súbelo cuando las capturas salgan a medio cargar.

### Web workers: el caso en que un segundo sí fue el límite

En un caso, el umbral fue mucho más bajo. En 2021 Dave Smart [probó si Google indexa contenido generado por web workers](https://tamethebots.com/blog-n-bits/will-web-worker-contents-index) (scripts que corren en segundo plano, fuera del hilo principal). El worker sencillo se renderizó bien; los que hacían un `fetch` fallaron siempre, aunque Googlebot pedía el recurso; y el que hacía un cálculo largo solo funcionó cuando devolvía el resultado en menos de un segundo. Es una prueba antigua y de una sola página, y Google ha reconocido que los web workers pueden dar resultados raros porque muy pocas webs los usan. Yo no dejaría en manos de un web worker nada que quieras indexar.

### Cómo medir cuándo te rastrea y cuándo te renderiza

Google te rastrea en un momento y te renderiza en otro, y solo el primero queda apuntado en un sitio que puedas consultar. Del rastreo tienes dos testigos: tus logs y la respuesta que guarda Search Console. Inspecciona la URL **sin darle a «Probar URL publicada»** y abre «Ver página rastreada» > «Más información» > «Respuesta HTTP»: la cabecera `Date` es la hora del rastreo.

![Respuesta HTTP de la página rastreada en Search Console, con la cabecera Date: Sat, 01 Nov 2025 06:24:00 GMT resaltada](https://seomanal.es/uploads/articulos/2-e2b1c34ae46dbc01.webp)
*La cabecera Date de la página rastreada es la hora del rastreo, no la del renderizado.*

Del renderizado no tienes ningún testigo. El WRS no vuelve a pedir tu HTML: trabaja con el que ya se descargó. Como mucho verás en los logs peticiones a tus JS y CSS, y ni eso es seguro, porque los tiene en caché hasta 30 días.

### El método de las tres metaetiquetas

Para asomarme al renderizado me monté un método casero con tres [metaetiquetas](https://sanchezdonate.net/seo-avanzado/metaetiquetas/) inventadas en el `<head>`: una que rellena el servidor con la fecha, otra que JavaScript reescribe con la fecha cada segundo y otra que JavaScript usa de contador. Las metaetiquetas, con la fecha de servidor en PHP (usa el lenguaje de servidor que tenga tu web):

```
<?php
date_default_timezone_set('Europe/Madrid');
$date = date('d/m/Y H:i:s');
?>
<meta name="user-date" content="<?php echo $date; ?>">
<meta name="user-date-js" content="<?php echo $date; ?>">
<meta name="snapshot-counter" content="0">
```

El script que reescribe la fecha cada segundo:

```
function updateMetaDate() {
  const metaTag = document.querySelector('meta[name="user-date-js"]');
  if (metaTag) {
    setInterval(() => {
      const now = new Date();
      const formattedDate = now.toLocaleString('es-ES', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      }).replace(',', '');
      metaTag.setAttribute('content', formattedDate);
    }, 1000);
  }
}
document.addEventListener("DOMContentLoaded", updateMetaDate);
```

Y el contador:

```
document.addEventListener('DOMContentLoaded', () => {
  const meta = document.querySelector('meta[name="snapshot-counter"]');
  let count = parseInt(meta.getAttribute('content')) || 0;
  const intervalId = setInterval(() => {
    count++;
    meta.setAttribute('content', count.toString());
  }, 1000);
  window.snapshotIntervalId = intervalId;
});
```

Pon las dos fechas en el mismo formato, que bastante lío hay ya con las zonas horarias. Cuando Google haya rastreado la versión con las metas, inspecciona la URL, abre «Ver página rastreada» y busca `user-da` en el HTML:

![Las tres metaetiquetas inventadas tal como las muestra Search Console: user-date 05/11/2025 21:14:51, user-date-js 04/11/2025 16:00:05 y snapshot-counter 5](https://seomanal.es/uploads/articulos/2-788f8f4eb804a4f3.webp)
*Las tres metaetiquetas en el HTML de la página rastreada, en Search Console.*

Cada meta dice una cosa distinta:

- `user-date` la escribe tu servidor, así que es la fecha real del último rastreo.
- `snapshot-counter` dice si tu JavaScript se ejecutó y cuántos ticks del reloj de Google llegó a contar. Ya sabes que no son segundos.
- `user-date-js` parece la hora del renderizado, y no lo es.

Con la tercera me equivoqué. En mis primeras capturas la fecha de JavaScript era anterior a la del rastreo, y llegué a pensar que la página se había renderizado casi un día después:

![Página rastreada en la inspección de URL de Search Console con user-date 11/01/2025 07:24:00 y user-date-js 31/10/2025 17:00:05](https://seomanal.es/uploads/articulos/2-7aafd04f89901b22.webp)
*Primer ejemplo: fecha de servidor del 1 de noviembre y fecha de JavaScript del 31 de octubre.*

La pista era la hora: en todas mis capturas, la fecha de JavaScript marcaba las 17:00:05 o las 16:00:05, según la época del año. Pasadas de la hora del Pacífico a UTC, son siempre las **00:00:05 UTC del día del rastreo**: medianoche más cinco ticks del contador. En la indexación real, el reloj del navegador de Google arranca a medianoche UTC, y otras pruebas han visto lo mismo. La fecha de JavaScript, por tanto, no da la hora del renderizado; como mucho orienta sobre el día.

En la prueba en vivo, en cambio, el reloj sí es el real:

![Prueba en vivo en Search Console con user-date 04/11/2025 12:25:13, user-date-js 04/11/2025 03:25:16 y snapshot-counter en 5](https://seomanal.es/uploads/articulos/2-2121f818f0efa418.webp)
*«Probar URL publicada»: aquí la fecha de JavaScript sí es una hora real, en hora del Pacífico.*

Ahí la fecha de JavaScript es una hora de verdad, pero la prueba en vivo es justo donde no estás midiendo el renderizado que indexa.

### Para medir tiempo real, que la hora la ponga tu servidor

Si dentro del navegador de Google no hay un reloj fiable, la hora la tiene que poner alguien de fuera: tu servidor. Hay dos formas:

- Que la página pida un recurso cuyo servidor devuelve su hora real, y apuntar esa hora dentro del propio HTML para leerla después en la inspección de URL.
- Que la página pida un texto a un servidor que tarda unos segundos en responder a propósito, como en la prueba de arriba. Si el texto aparece en el HTML renderizado, Google esperó al menos ese tiempo real, diga lo que diga el contador.

Para apuntar cosas dentro del HTML sin enviar nada a tu servidor, sirve un registro dentro de la propia página:

```
<script type="text/plain" id="html-log"></script>
<script>
  const registro = document.getElementById('html-log');
  function htmlLog(mensaje) {
    registro.appendChild(document.createTextNode(mensaje + '\n'));
  }
  htmlLog('User Agent: ' + navigator.userAgent);
  addEventListener('load', () => htmlLog('Evento: load'));
  addEventListener('error', (e) => htmlLog('Error: ' + e.message));
</script>
```

Los mensajes quedan dentro del HTML renderizado y los lees en la inspección de URL o en la prueba de resultados enriquecidos. Para los errores de JavaScript, [Google propone](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript) otra vía: un listener del evento `error` en `window` que envíe el mensaje, el fichero y la línea a un servicio de registro, para enterarte de lo que falla en tus usuarios y también en Googlebot.

Dos trampas. La primera, la caché: Google cachea los recursos con ganas, y en estas pruebas acabarás viendo respuestas antiguas de tu servidor. La segunda, que no puedes fabricar un identificador único en el navegador, porque dentro de Google `Math.random()` devuelve siempre lo mismo y la hora está fijada. Las salidas son pedir por POST o que tu servidor imprima un identificador distinto en el HTML de cada carga. Y hazlo en una página de pruebas, no en toda la web: cada URL de recurso nueva gasta crawl budget.

Por último, mira siempre dónde lees cada resultado. En la prueba en vivo sabrás cómo se comporta la herramienta; en la página rastreada, cómo se comporta el Googlebot real. Hay que probar las dos cosas, y la que cuenta es la segunda.

## Estrategias de renderizado: CSR, SSR, estático, ISR, hidratación y renderizado dinámico

Cuando trabajas con un framework de JavaScript tienes que decidir **dónde se renderiza tu web**: en el navegador del usuario, en el servidor en cada petición o por adelantado, en el momento del deploy. Es la idea de la plantilla y los datos del primer capítulo: cada estrategia junta las dos cosas en un sitio y en un momento distinto. Con Google se puede posicionar con cualquiera. Lo que cambia es el coste y quién lo paga: el servidor, el usuario o el rastreo.

![Esquema del funcionamiento de una web: cómo viaja una página desde el servidor hasta el navegador](https://seomanal.es/uploads/articulos/2-87bdef2fc5166f24.webp)
*Cómo viaja una página del servidor al navegador. La estrategia de renderizado decide en qué punto del viaje se monta el HTML.*

### La tabla comparativa: CSR, SSR, SSG, DSG e ISR

Los cinco métodos y, al final, dos filas que no son métodos puros, pero que conviene tener a la vista:

| Tipo | Descripción | Ventajas SEO | Consideraciones |
| --- | --- | --- | --- |
| CSR (Client-Side Rendering) | El navegador descarga el JavaScript y construye la página. | Ninguna propia. Con Google se puede posicionar si está bien desarrollado. | Contenido y enlaces solo existen tras el render. Es el más frágil, el JS crece con la aplicación (peor INP, la respuesta a las interacciones) y los rastreadores que no ejecutan JS no ven nada. |
| SSR (Server-Side Rendering) | El servidor genera el HTML en cada petición. | Contenido, enlaces y metaetiquetas en el HTML inicial. | Carga al servidor y puede empeorar el TTFB (lo que tarda en llegar el primer byte). Pide caché o un buen CDN. |
| SSG (Static Site Generation) | El HTML se genera en el deploy y se reutiliza en cada petición. | TTFB rápido y estable. Todo está en el HTML inicial. | Cualquier cambio obliga a rehacer prácticamente el deploy entero. No sirve si no puedes prever las URL. |
| DSG (Deferred Static Generation) | Como el SSG, pero difiere ciertas páginas hasta que alguien las pide y luego las cachea. | Las del SSG en las páginas ya generadas. | Concepto principalmente de Gatsby. Menos personalizable que el ISR. |
| ISR (Incremental Static Regeneration) | Estático que se regenera por páginas, sin reconstruir el sitio entero. | Las del SSG sin su gran desventaja. | La primera visita a una página aún no generada se parece a un SSR. Mi opción si puedo elegir en un proyecto grande. |
| Híbrido | Lo que importa al SEO sale del servidor o es estático; lo dinámico se renderiza en el cliente. | Descubrimiento rápido de contenido y enlaces, con interactividad donde hace falta. | Complejo de implementar y de mantener. |
| Renderizado dinámico | CSR para los usuarios y una versión prerrenderizada para ciertos user-agents. | Ninguna de cara a Google. | Desaconsejado. Google lo trata como un parche y no lo recomienda. |

Para el rendimiento, la referencia es [Rendering on the Web](https://web.dev/articles/rendering-on-the-web), de web.dev. Compara las arquitecturas con cuatro métricas: TTFB, FCP (cuándo se ve el contenido), INP y TBT (cuánto tiempo estuvo bloqueado el hilo principal durante la carga). Para el rastreo, la tabla más citada es la de Vercel y MERJ, que pone al SSG y al ISR como excelentes, al SSR como muy bueno y al CSR como pobre. Esa tabla es una opinión de sus autores: en su propia muestra todas las páginas indexables acabaron renderizadas.

### CSR: se puede posicionar en Google, pero sabiendo lo que cuesta

En CSR todo el front, incluidas las etiquetas HTML, sale de JavaScript. La ventaja es para el servidor, que sufre muchísimo menos porque el trabajo lo hace cada usuario en su dispositivo.

![Esquema del Client-Side Rendering: el navegador del usuario descarga el JavaScript y construye la página](https://seomanal.es/uploads/articulos/2-fd5c34c2fa7be236.webp)
*Client-Side Rendering: el trabajo de montar la página lo hace el navegador.*

El problema se ve rápido con cualquier web CSR pura: ni en el código fuente ni en un rastreo estándar con Screaming Frog aparecen enlazado interno, metaetiquetas o contenido. Solo salen si activas el renderizado de JavaScript, y entonces el rastreo tarda mucho más.

![Configuración del renderizado de JavaScript en Screaming Frog](https://seomanal.es/uploads/articulos/2-3ffca4be40ad65b3.webp)
*Sin esta opción, Screaming Frog rastrea el HTML inicial y una web CSR le parece vacía.*

El CSR no penaliza: preguntado por ello, Google respondió que, si la página está bien desarrollada, no debería haber problema alguno para posicionarla. Aun así, su documentación sigue diciendo que el renderizado en servidor o el prerrenderizado son una gran idea, porque hacen la web más rápida y porque no todos los bots ejecutan JavaScript. Lo que cuesta el CSR son cuatro cosas:

- **El descubrimiento de enlaces.** Si los enlaces solo existen tras ejecutar JS, cada nivel de profundidad exige rastrear, renderizar y volver a encolar (lo conté en [las dos olas](#enlaces-js)).
- **La fragilidad.** Un recurso bloqueado en el robots.txt, una API que no responde a los bots o un error de JS, y la página se queda vacía.
- **El crawl budget en sitios grandes.** Vercel y MERJ sitúan el umbral en [unas 10.000 páginas únicas que cambian a menudo](https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process). La [documentación de Google](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) solo pide vigilar el crawl budget a partir de un millón de URL que cambian cada semana o de 10.000 que cambian a diario, y no lo relaciona con el renderizado. Qué es y cómo se mide, en [crawl budget](https://sanchezdonate.net/articulo/el-presupuesto-de-rastreo/).
- **Los rastreadores que no ejecutan JavaScript.** Bing y las IA tienen [su propio capítulo](#fuera-de-google).

Todo eso exige un esfuerzo extra, y el HTML inicial de cada página debe ser indexable antes de que el JS toque nada.

### SSR y renderizado estático: el contenido, en el HTML desde el primer byte

En SSR el JavaScript se ejecuta en el servidor en cada petición. Cuando llega la respuesta, el contenido, los enlaces y las metaetiquetas ya están ahí, para Google y para cualquier bot que solo lea HTML.

![Esquema del Server-Side Rendering: el servidor renderiza el JavaScript y envía el HTML ya montado](https://seomanal.es/uploads/articulos/2-ee1bac65eb35b9ca.webp)
*Server-Side Rendering: el HTML llega montado desde el servidor.*

Es la solución más común cuando se trabaja con un framework de JS, porque resuelve de un plumazo los problemas de indexación, y suele dar un FCP rápido porque envías menos JavaScript. No es milagrosa: generar la página en el servidor lleva tiempo y puede empeorar el TTFB, y a menudo se procesa la misma aplicación dos veces, en servidor y en cliente. En webs con muchas visitas necesitas un servidor potente, caché de HTML o [un buen CDN](https://sanchezdonate.net/articulo/cdn/).

El renderizado estático hace lo mismo, pero una sola vez: genera el HTML en el deploy y lo reutiliza en cada petición. Por eso su TTFB es rápido y estable.

![Esquema del renderizado estático: el HTML se genera en el deploy y se reutiliza en cada petición](https://seomanal.es/uploads/articulos/2-f54d5a7f53939944.webp)
*Renderizado estático: el HTML se genera una vez y se sirve tal cual.*

De sus tres sabores (SSG, DSG e ISR, los de la tabla), el que lo hace escalable es el ISR, porque regenera páginas sueltas sin reconstruir el sitio entero. En Next.js, por ejemplo, son unas pocas líneas: la página se sirve estática y se regenera en segundo plano como mucho una vez por hora.

```
// app/productos/[slug]/page.js
export const revalidate = 3600; // segundos

// Ninguna página en el build: cada una se genera en su primera visita y queda en caché
export async function generateStaticParams() {
  return [];
}

export default async function Producto({ params }) {
  const { slug } = await params; // desde Next.js 15, params es una promesa
  const producto = await obtenerProducto(slug);
  return <h1>{producto.nombre}</h1>;
}
```

Estático no significa sin JavaScript. Se puede seguir llamando a API y añadiendo secciones en CSR. Para saber qué tienes hay una prueba muy simple: desactiva JavaScript y carga la página. Si casi todo sigue funcionando, es estática de verdad.

### Híbrido, hidratación e islas no son sinónimos

Estas palabras se usan como si fueran lo mismo (yo también lo he hecho, llamando «rehydration» al híbrido), y mezclarlas impide entender las críticas que recibe cada una:

- **Hidratación.** El HTML llega completo del servidor y el JavaScript del cliente le añade estado e interactividad.
- **Rehidratación.** Se usa a menudo como sinónimo, pero en rigor implica seguir actualizando el DOM con el último estado después de la hidratación inicial.
- **Rehidratación progresiva o parcial.** Las piezas se van activando con el tiempo, o solo se activan las interactivas. La parcial ha resultado difícil de implementar.
- **Islas.** Un HTML de servidor con widgets independientes que se hidratan cada uno por su cuenta ([Islands Architecture](https://jasonformat.com/islands-architecture/)).
- **Híbrido.** Es el paraguas: lo que importa al SEO sale del servidor y lo dinámico se deja al cliente, por piezas o por rutas.

![Esquema del renderizado híbrido: parte del JavaScript se procesa en el servidor y parte en el navegador del usuario](https://seomanal.es/uploads/articulos/2-c2d6c69f59a7f2a7.webp)
*Renderizado híbrido: buscadores y personas ven la misma versión, pero no todo se monta en el mismo sitio.*

La hidratación es segura para indexar: el contenido ya viene en el HTML inicial, y [Google la propone](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) junto al SSR y al renderizado estático como alternativa al renderizado dinámico. Que el contenido y los enlaces vayan repetidos en el HTML y en los datos de hidratación tampoco tiene efectos secundarios: Google descubre los enlaces dos veces y ya está.

Las críticas que recibe son de ingeniería. El servidor manda la interfaz, los datos con los que se compuso y una copia completa de la aplicación que vuelve a arrancar en el cliente: una aplicación por el precio de dos. La página parece lista y no responde hasta que se ejecuta el JS, y eso se paga en TBT e INP. Por eso web.dev recomienda, antes que una rehidratación completa, SSR o renderizado estático.

Mi regla (lo que afecte al SEO, en el HTML del servidor) se cumple igual con hidratación completa, con islas o con un híbrido por rutas. Lo que cambia es cuánto pagas en interactividad.

### Streaming: qué recibe un rastreador cuando el HTML llega a trozos

Con streaming el servidor no espera a tener la página entera. Manda primero la carcasa (layout, navegación y los huecos de carga) y después cada bloque según se resuelve, todo dentro de la misma respuesta HTTP.

En Next.js y en React, el contenido que llega tarde viene en el HTML dentro de un `div` oculto, y un script lo coloca en el hueco que le corresponde. Un cliente sin JavaScript recibe el texto, pero escondido y fuera de su sitio. Con Google no debería suponer un problema: el contenido servido en streaming se ha visto renderizado entero.

Para el resto, Next.js distingue por user-agent. Con los bots que llama «HTML-limited» espera a tener los metadatos resueltos, para que vayan en el `head`. Su [lista por defecto](https://nextjs.org/docs/app/api-reference/config/next-config-js/htmlLimitedBots) incluye Bingbot, Twitterbot, Slackbot y algunos rastreadores de Google como AdsBot-Google, y se puede cambiar con la opción `htmlLimitedBots`. A los demás, Googlebot incluido, les puede servir los metadatos en streaming, añadidos al `body` cuando se resuelven; Next.js [dice haber verificado](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) que los bots que ejecutan JavaScript los interpretan bien. Yo lo comprobaría igualmente en la inspección de URL.

React documenta además un patrón para rastreadores: enviar el HTML completo de una vez (`onAllReady`) en lugar del stream progresivo (`onShellReady`) cuando detectas un bot. Eso es una respuesta distinta por user-agent, y se parece mucho al renderizado dinámico, con la diferencia de que el contenido es el mismo y solo cambia cómo se entrega. El coste: el rastreador tiene que esperar a todos los datos, y alguno puede tardar o fallar.

#### Streaming y códigos de estado: cuando el 200 ya ha salido

El streaming trae un problema nuevo: en cuanto el servidor empieza a enviar, las cabeceras y el código de estado ya han salido. Y han salido como 200.

[Next.js lo explica](https://nextjs.org/docs/app/guides/streaming): si `notFound()` salta a mitad del stream, ya no puede devolver un 404, así que inyecta un `<meta name="robots" content="noindex">` en el HTML para que los buscadores no indexen la página. Y un `redirect()` a mitad del stream se convierte en una redirección en cliente. Para tener un 404 de verdad hay que llamar a `notFound()` antes de cualquier `await` o límite de `Suspense`, por ejemplo con una comprobación rápida de que el slug existe. [React](https://react.dev/reference/react-dom/server/renderToPipeableStream) reconoce el mismo compromiso: un error fuera de la carcasa inicial de la página puede acabar servido con un 200.

En la práctica, lo que hace Next.js es la segunda salida de Google para los soft 404: un 200 con un noindex que llega tarde. Funciona, pero depende de que ese noindex se lea.

### Renderizado dinámico: de recomendación a idea terrible

El renderizado dinámico consiste en detectar a los rastreadores, normalmente por user-agent, y servirles una versión prerrenderizada en HTML estático mientras los usuarios reciben la versión CSR.

![Esquema del renderizado dinámico: los usuarios reciben la versión CSR y los rastreadores una versión HTML prerrenderizada](https://seomanal.es/uploads/articulos/2-813597b73136d59d.webp)
*Renderizado dinámico: dos caminos distintos según quién pida la página.*

Su historia es la de una recomendación que caducó:

1. En 2019 Google lo presentaba como una opción a considerar si tu web dependía mucho del JavaScript en cliente.
2. En agosto de 2022 actualizó su documentación para decir que es un parche y no una solución a largo plazo.
3. Hoy [no lo recomienda](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering), porque añade complejidad y consumo de recursos, y propone en su lugar SSR, renderizado estático o hidratación. En sus [diapositivas de Andalu-SEO 2026](https://andalu-seo.es/ponente/2026/pdf/04-rendering-golden-circle-martin-splitt.pdf), Martin Splitt lo despacha con «Terrible idea. Avoid».

![Captura de la actualización de agosto de 2022 de la documentación de Google sobre los problemas del renderizado dinámico](https://seomanal.es/uploads/articulos/2-57cbb5a3bbb88253.webp)
*La actualización de la documentación de Google del 10 de agosto de 2022.*

Dos aclaraciones. La primera: **no es cloaking**. Google dice que en general no lo considera encubrimiento mientras el contenido sea similar. Cloaking es servir una página de gatos a los usuarios y una de perros a los rastreadores. La segunda: lo desaconseja por coste y complejidad; su documentación no habla de penalización en ningún momento.

Si ves el renderizado dinámico recomendado en una auditoría o en una presentación, probablemente venga de [la página de web.dev](https://web.dev/articles/rendering-on-the-web), que también es de Google y aún arrastra la frase de 2019 en su párrafo de SEO. Es un desfase entre dos webs de Google, y manda Search Central.

Como parche para las IA que no ejecutan JavaScript sigue siendo defendible cuando no puedes tocar el front; lo valoro en [el capítulo de fuera de Google](#que-hacer-ia).

### Cómo elegir, y por qué yo me quedo con el ISR

No hay una estrategia correcta para todo: depende de cuánto cambie tu contenido, de la infraestructura y el equipo que tengas y de para qué sirva la web. Piénsalo como herramientas: preguntar si es mejor el SSR o el CSR es como preguntar si necesitas un martillo o un destornillador. Si tu web presenta información (un blog, un medio, una tienda), exigir JavaScript es una desventaja: puede romperse, va más lento y gasta más batería, y ahí toca SSR, prerrenderizado o directamente HTML estático. Si lo que construyes es una aplicación muy interactiva, como un editor de vídeo o un CAD, el CSR tiene sentido.

También hay que saber cuándo no tocar nada. Rehacer un ecommerce que pinta sus productos con JavaScript es, a todos los efectos, una migración, y no lo recomendaría sin datos sólidos de que el problema lo causa el JavaScript.

Mi recomendación: para un proyecto pequeño y estable, SSG. Para contenido que cambia en cada petición o es personalizado, SSR con una buena caché. Y para un proyecto grande con muchas actualizaciones que quiere estar optimizado para SEO, **ISR**: tiene los beneficios del estático sin morir en el intento cada vez que se hace un deploy.

![Informe de Lighthouse de una web con Incremental Static Regeneration](https://seomanal.es/uploads/articulos/2-3bcda5d7e64ae7a7.webp)
*Lighthouse de una web servida con ISR.*

## Cambiar lo que Google lee sin tocar el código de la web

No necesitas tocar el código de una web para cambiar lo que Google termina leyendo de ella. Cuando el CMS es rígido, o cuando el programador te dice que «la implementación que pides no se puede hacer en el código de esta página», quedan dos vías: el servidor y JavaScript. Van en ese orden a propósito, de más limpia a menos. Lo que cambias en el servidor va en el HTML inicial y lo ven Google, Bing y las IA; **lo que cambias con JavaScript solo lo ve quien renderiza**.

### Primero el servidor: output buffering

> El output buffering consiste en retener el HTML final que se iba a enviar y modificarlo antes de que le llegue al usuario.

PHP guarda la salida en memoria con `ob_start()`, tú la retocas y después se envía (lo expliqué con detalle en [mi artículo sobre el buffer de PHP](https://sanchezdonate.net/articulo/buffer-php-modificar-html/)). También se puede hacer en webs con Python, Java o Ruby. La ventaja sobre JavaScript es que el cambio aparece en el HTML inicial como si siempre hubiera estado ahí. Con él puedes:

- Modificar el HTML final aunque la web esté hecha con un «código espagueti» que nadie entienda (documenta bien los cambios, o el siguiente que llegue no sabrá de dónde salen).
- Cambiar el estado de indexación de cualquier página.
- Eliminar, modificar o añadir cualquier etiqueta en una página o en un grupo de páginas.
- Quitar JS y CSS de las páginas donde no se usan, lo que de paso mejora el WPO.
- Modificar encabezados o montar un sistema de enlazado automático.

Es decir, cualquier cambio que harías con JavaScript, salvo la interacción. Este ejemplo funcionaría en WordPress, Drupal, PrestaShop, Laravel o PHP sin framework, y añade la clase `buffering-is-working` al h1 de la home:

```
<?php
ob_start(function ($buffer) {
    $is_home = ($_SERVER['REQUEST_URI'] == '/' || $_SERVER['REQUEST_URI'] == '/index.php');
    if ($is_home) {
        $buffer = preg_replace_callback(
            '/<h1([^>]*)>/i',
            function ($matches) {
                $attributes = $matches[1];
                if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $classMatches)) {
                    $existingClasses = $classMatches[1];
                    $newClasses = trim($existingClasses . ' buffering-is-working');
                    $attributes = preg_replace(
                        '/class=["\'][^"\']*["\']/',
                        'class="' . $newClasses . '"',
                        $attributes
                    );
                } else {
                    $attributes .= ' class="buffering-is-working"';
                }
                return '<h1' . $attributes . '>';
            },
            $buffer
        );
    }
    return $buffer;
});

register_shutdown_function(function () {
    if (ob_get_length() > 0) {
        ob_end_flush();
    }
});
?>
```

Si al cargar la home ves esa clase en el código fuente (Ctrl+U, no el inspector de elementos), el buffer funciona. A partir de ahí cambias la expresión regular por la modificación que necesites.

Basta de ser cutres escondiendo con el clásico `display:none` lo que sobra: con el buffer, directamente, no sale en el HTML. Si combinas el buffer con una buena caché, sirve incluso en webs montadas con constructores visuales como Elementor o Divi. En WordPress yo lo engancho a los hooks `after_setup_theme` y `shutdown` desde el functions.php o desde un plugin propio. Y pruébalo antes en un entorno de desarrollo: estás tocando todo el HTML que sale del servidor.

### JavaScript como lenguaje parche

Como Google indexa el HTML renderizado y saca los enlaces de ahí, un script puede modificar la web a tu antojo, esté hecha con lo que esté hecha: anchor text, href, atributos `rel` o `target`, encabezados que te cuela un plugin de cookies, el title, la meta description o un JSON-LD entero (lo desarrollé en [JS para mejorar tu contenido](https://sanchezdonate.net/articulo/js-para-mejorar-tu-contenido/)). Para que Google lo tenga en cuenta se tienen que cumplir seis condiciones:

1. La URL debe ser rastreable, y tus scripts también: Google no ejecuta JavaScript de archivos bloqueados por robots.txt.
2. La URL debe ser indexable en el HTML inicial. Un noindex en origen no se arregla con JS.
3. La canonical debe ser correcta en origen: [self-canonical](https://sanchezdonate.net/articulo/self-canonical/) o ninguna.
4. Debe haber alguna forma de insertar el script (suele ser más fácil de lo que parece).
5. El script debe ejecutarse después de que exista el elemento que quieres cambiar. También vale un [defer](https://sanchezdonate.net/articulo/cargas-diferidas-seo/#defer).
6. El cambio no debe depender de temporizadores largos ni de interacción.

La última la formulaba antes como «la web debe ser lo suficientemente rápida». Con lo que sabemos del [reloj de Google](#reloj-virtual), tu parche no se queda fuera porque el servidor vaya lento. Se queda fuera si lo escondes detrás de un temporizador largo o de un clic.

### Ejemplos: enlaces, atributos y los encabezados de OneTrust

Se puede modificar prácticamente cualquier elemento; lo único que hay que saber es cómo seleccionarlo. Imaginemos este enlace:

```
<a href="https://example.com" class="example-link" target="_blank" rel="nofollow">Haz clic aquí</a>
```

Con JavaScript podríamos hacer distintas cosas:

```
// Seleccionamos el enlace con la clase "example-link"
const link = document.querySelector('.example-link');

// Modificar el anchor text
link.innerHTML = 'Este es el nuevo Anchor Text';

// Cambiar la ruta del enlace
link.href = 'https://nuevo-enlace.com';

// Eliminar el target="_blank"
link.removeAttribute('target');

// Cambiar el nofollow por noopener
if (link.rel === 'nofollow') {
  link.rel = 'noopener';
}

// Meter el enlace dentro de otra etiqueta (al final: outerHTML sustituye el nodo
// y la variable link deja de apuntar al elemento que queda en el DOM)
link.outerHTML = `<div class="container">${link.outerHTML}</div>`;
```

Un ejemplo real: el banner de cookies de OneTrust inyecta encabezados que no pintan nada en la jerarquía de la página. Este script los convierte en `div` conservando atributos y contenido, de una forma mucho más simple que desde la propia plataforma:

```
// Seleccionar el elemento contenedor por ID
const container = document.getElementById('onetrust-pc-sdk');
if (container) {
  // Seleccionar todos los headings dentro del contenedor
  const headings = container.querySelectorAll('h1, h2, h3, h4');
  headings.forEach(heading => {
    // Crear un nuevo elemento div
    const div = document.createElement('div');
    // Copiar todos los atributos del heading al div
    Array.from(heading.attributes).forEach(attr => {
      div.setAttribute(attr.name, attr.value);
    });
    // Copiar el contenido del heading al div
    div.innerHTML = heading.innerHTML;
    // Reemplazar el heading con el div en el DOM
    heading.replaceWith(div);
  });
}
```

Asegúrate de que tu script cargue después que el de OneTrust. Si se ejecuta antes, el contenedor todavía no existe y no hay nada que cambiar.

### Dónde meter los scripts: tu web, Tag Manager o Patches

En tu propia web basta con algún sitio del back desde el que cargar un archivo JavaScript externo o añadir las etiquetas `script` en el HTML; si no sabes cómo, lo explico en [cómo modificar una web sin saber programación](https://sanchezdonate.net/articulo/modifica-wordpress-ia-nocode/). Si no tienes acceso, puedes hacerlo desde Google Tag Manager, que al fin y al cabo es un script que permite insertar otros scripts:

![Script personalizado insertado en una etiqueta de Google Tag Manager](https://seomanal.es/uploads/articulos/2-56943ff2c01cdea0.webp)
*Un script propio dentro de una etiqueta de Google Tag Manager.*

Por eso los datos estructurados o incluso un noindex se pueden implementar desde Tag Manager: Google ve el DOM renderizado. Lo de los datos estructurados lo recoge la propia Google en su [guía para generar datos estructurados con JavaScript](https://developers.google.com/search/docs/appearance/structured-data/generate-structured-data-with-javascript), con Tag Manager como una de las opciones.

La tercera opción es Patches de Ahrefs, que usa el mismo principio sin necesidad de saber JavaScript. Está en Site Audit y es de pago.

![Función Patches de Ahrefs dentro de Site Audit para modificar elementos de una web sin escribir JavaScript](https://seomanal.es/uploads/articulos/2-59d7a07245f2a35f.webp)
*Patches, dentro de Site Audit de Ahrefs.*

### Los límites del parche

- **El parpadeo.** El cambio se hace cuando se ejecuta el JS, así que a veces el usuario llega a ver el contenido original antes que el modificado. Es el precio de no tocar el servidor.
- **Bing y las IA.** Lo hecho por JS no lo verán Bing ni la mayoría de los chats de IA, como cuento en [su capítulo](#fuera-de-google).
- **Lo que no se arregla con JS.** La canonical se arregla en origen (para eso está el buffer) y un noindex del HTML inicial no se quita con JS, como viste en [el capítulo del HTML inicial](#html-inicial). El JSON-LD, el title y la meta description sí se pueden poner o cambiar con JavaScript.
- **Los cambios a mitad de carga.** El mismo mecanismo que hace que un noindex inyectado por Tag Manager funcione puede hacer peligroso un meta robots o una canonical que cambian de valor durante la carga. Si parcheas con JS o trabajas con un framework, pasa el fragmento del [flashazo de noindex](#flashazo).

### ¿Cómo compruebas que Google ha visto el cambio?

No te fíes del inspector de tu navegador: tú sí ejecutas todos los scripts, y a tiempo. Para Google, la inspección de URL de Search Console (la página rastreada, no la prueba en vivo). Si no tienes acceso, la prueba de resultados enriquecidos, siempre con la URL y no pegando el código. Para ver el HTML final de cualquier URL también sirve el Page Inspect de Ahrefs, aunque no esté hecho para eso:

![Page Inspect de Ahrefs mostrando el HTML final renderizado de una página](https://seomanal.es/uploads/articulos/2-f6e15e7a32b78326.webp)
*El HTML final de una página visto en Page Inspect de Ahrefs.*

Para Bing y las IA la comprobación es más corta: Ctrl+U. Si el cambio no está en el código fuente, dalo por no visto.

## Cómo comprobar lo que Google ha renderizado

No todas las herramientas responden a la misma pregunta. Unas te dicen lo que Google **tiene** de tu página, otras lo que **vería** si pasase ahora mismo, y otras solo lo que ve un Chrome que se le parece. Cuanto más lejos esté la herramienta del Googlebot real, más te sirve para depurar y menos para afirmar nada.

| Herramienta | Qué responde | Qué no responde |
| --- | --- | --- |
| Inspección de URL, versión indexada («Ver página rastreada») | Qué HTML, cabeceras, consola y recursos tiene Google del último rastreo | Cómo se ve (no hay captura) ni si el cambio que has subido hoy funciona |
| Inspección de URL, prueba en vivo («Probar URL publicada») | Cómo renderizaría Google la URL ahora mismo, con captura | Qué hay indexado, qué canónica elegirá Google, si es un duplicado |
| Prueba de resultados enriquecidos | Lo mismo que la prueba en vivo, en webs ajenas y también en escritorio | Nada sobre el índice |
| Logs de servidor y estadísticas de rastreo | Qué URL y qué recursos pide Google de verdad | Qué hizo con ellos al renderizar |
| Fetch & Render de Dentsu | La captura de la página entera | No es Google: sin garantía de que el resultado sea el real |
| Screaming Frog, Page Inspect de Ahrefs | Diferencias entre HTML inicial y renderizado, en muchas URL a la vez | No son Googlebot: otra IP, otro Chrome, otros tiempos |
| Tu navegador | Qué depende de JavaScript y qué cambia en el DOM | Lo que capturó Googlebot |

### Inspección de URL: versión indexada frente a prueba en vivo

La inspección de URL y «Probar URL publicada» son dos herramientas distintas dentro de la misma pantalla, y confundirlas es un error muy habitual al auditar el renderizado.

Cuando inspeccionas una URL sin tocar nada más, lo que ves es [la última versión indexada](https://support.google.com/webmasters/answer/9012289?hl=en), no la que hay ahora en la web. En «Ver página rastreada» tienes el HTML, las cabeceras HTTP, la salida de la consola de JavaScript y los recursos cargados. Ese HTML ya lleva aplicados los cambios de tu JavaScript (ahí es donde leo yo las metaetiquetas de mis pruebas) y es lo que Google tiene de verdad. Eso sí, **sin captura de pantalla**.

«Probar URL publicada» es una prueba en vivo: Google pide la URL en ese momento, la examina, añade la captura y te enseña cómo se vería si pasase ahora. No predice si esa versión será la canonical, no detecta duplicados y nunca te dirá «Rastreada: actualmente sin indexar». La canonical que elige Google solo está en los datos indexados.

Las dos usan el mismo navegador, pero no en las mismas condiciones:

| Condición | Prueba en vivo | Googlebot real (indexación) |
| --- | --- | --- |
| Caché de recursos | Pide los recursos frescos | Tira de su caché: JS y CSS de hasta 30 días |
| Reloj | Hora real | Arranca a medianoche UTC |
| Agente | Google-InspectionTool (`Mozilla/5.0 (compatible; Google-InspectionTool/1.0)` en escritorio), con captura | Googlebot, sin captura |
| IP | Según la [documentación](https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers), los rangos de `common-crawlers.json`, los mismos que Googlebot | Los rangos de `common-crawlers.json` |
| Espera máxima ante una página que no para de pedir datos | Entre 48 y 54 segundos en una prueba publicada | Unos 30 segundos en esa misma prueba, con cifras muy variables |

La regla práctica: **prueba en vivo para depurar; HTML de la página rastreada para saber qué hay indexado**.

### Qué es (y qué no es) la captura de pantalla

La captura solo existe en la prueba en vivo y solo si la prueba sale bien. La genera Google-InspectionTool, no Googlebot. **Una captura nunca es una prueba de lo que Google indexó**. Sirve para detectar errores que con solo el DOM no verías, como un hero gigante, y nada más.

Si no se parece a lo que esperabas, mira primero los recursos: puede que estén bloqueados para Google-InspectionTool, que no es Googlebot, en tu servidor, en tu CDN o en tu firewall. Y si lo que ves es un bloque enorme que lo tapa todo, ve al [100vh](#cien-vh).

### La captura corta a 1750 px (y la de escritorio, a 10.000)

La captura además tiene un tope de altura, y Google no lo documenta. La idea de medirlo fue de Mario Mangas: montamos páginas con marcas de corte cada ciertos píxeles y las probamos en dos webs distintas, con distintos dispositivos y tipos de solicitud (el experimento completo está en [Donde corta el renderizado de Search Console](https://sanchezdonate.net/articulo/curiosidades/donde-corta-el-renderizado-de-search-console/)).

![Página de prueba con marcas de corte cada ciertos píxeles vista en el visor de renderizado de Search Console](https://seomanal.es/uploads/articulos/2-e783e0b5abe6599d.webp)
*Una de las páginas con marcas de píxeles, en el visor de renderizado de Search Console.*

![Segunda prueba del corte de la captura de Search Console, que termina en la marca de 1750 píxeles](https://seomanal.es/uploads/articulos/2-34de8a427049d826.webp)
*La segunda prueba: la captura termina en el mismo punto.*

El resultado fue el mismo en todos los casos: **la captura se corta a los 1750 px de altura**. Lo que queda por debajo no sale en la imagen, pero sigue en el HTML renderizado. Es un límite de la herramienta. El riesgo es humano: quien audita mirando la captura puede pasar por alto un elemento importante porque no entra en esos 1750 px.

Para ver más abajo, la [prueba de resultados enriquecidos](https://search.google.com/test/rich-results?hl=es), en modo ordenador, enseña hasta 10.000 px:

![Prueba de resultados enriquecidos de Google con la opción de probar la URL como ordenador](https://seomanal.es/uploads/articulos/2-c89a67d4e803a650.webp)
*En la prueba de resultados enriquecidos puedes elegir probar como ordenador.*

![Captura de escritorio de la prueba de resultados enriquecidos, que llega hasta 10.000 píxeles de alto](https://seomanal.es/uploads/articulos/2-2e135c45406caae1.webp)
*La captura de escritorio llega hasta los 10.000 px.*

No es lo que dicta el mobile first indexing, pero sirve para hacerse una idea.

### Prueba de resultados enriquecidos: la opción para webs ajenas

Cuando no tienes acceso a la Search Console del proyecto, la prueba de resultados enriquecidos te da el HTML renderizado, la consola, los recursos y una captura de cualquier URL pública. Es lo más exacto **de lo disponible para webs ajenas**, y así detecté el caso de Pandora del 100vh. Pero sigue siendo una prueba en vivo: no te dice qué hay indexado.

### Fetch & Render de Dentsu: toda la página, con reservas

La herramienta [Fetch & Render de Dentsu](https://technicalseo.com/tools/fetch-render/) (antes Merkle) no tiene límite de altura: te enseña el renderizado de toda la página.

![Interfaz de Fetch and Render de Dentsu con el renderizado completo de una página, sin límite de altura](https://seomanal.es/uploads/articulos/2-ab77de5a2a5a3d33.webp)
*Fetch & Render de Dentsu: la página completa.*

La pega es que no es oficial. No suele fallar, pero a veces se aprecian diferencias con Search Console. Yo la uso solo para ver qué hay por debajo del corte. Si no coinciden, gana Search Console.

### Screaming Frog y otros rastreadores: una simulación útil, no Googlebot

Para auditar miles de URL no puedes ir inspeccionando una a una. Ahí entra Screaming Frog con el renderizado de JavaScript activado (Configuration > Spider > Rendering). Lo que más uso, según [su tutorial](https://www.screamingfrog.co.uk/seo-spider/tutorials/crawl-javascript-seo/):

- La pestaña JavaScript y sus filtros. Los que más miro: «Noindex Only in Original HTML», «Canonical Only in Rendered HTML», «Canonical Mismatch», «Contains JavaScript Content», «Contains JavaScript Links» y «Pages with Blocked Resources».
- «Show Differences»: si guardas el HTML original y el renderizado, te hace el diff entre los dos, con un filtro para ver qué texto visible solo existe tras ejecutar JavaScript.
- La pestaña «Rendered Page», con una captura, muy útil en staging, donde no puedes usar la inspección de URL.

Ojo con el tiempo de espera AJAX: sus 5 segundos por defecto no tienen nada que ver con los de Google (lo explico en [el misterio de los 5 segundos](#cinco-segundos)).

Y no olvides que es una simulación: tiene su propia implementación de renderizado, puede ir con otra versión de Chrome y rastrea desde otra IP, así que tu servidor puede servirle otra cosa. Lo mismo vale para Ahrefs y su Page Inspect. Cuando no coinciden con Search Console, manda la inspección de URL.

### En tu navegador: sin JavaScript, código fuente frente a DOM y red limitada

Tres comprobaciones que no necesitan ninguna herramienta externa:

1. Desactiva JavaScript y recarga. Lo que desaparece es lo que depende del renderizado. Que la página se quede en blanco no es un problema por sí mismo: solo te indica dónde mirar después en el HTML renderizado.
2. Compara Ctrl+U con el inspector de elementos: el HTML que llega del servidor frente al DOM después del JavaScript.
3. Limita la red desde DevTools y mira cuánto JavaScript se descarga antes de que la página sea interactiva.

Ctrl+U y el inspector solo enseñan el principio y el final; para lo que pasa entre medias está el fragmento del [flashazo](#flashazo).

### Logs y estadísticas de rastreo: qué recursos pide Google de verdad

Como el WRS descarga los recursos a través de Googlebot, las peticiones de JavaScript, CSS y API que necesita el renderizado quedan en tus logs. Para Google, [es la mejor fuente](https://developers.google.com/search/blog/2024/12/crawling-december-resources) para saber qué recursos rastrea; la segunda, el informe de estadísticas de rastreo de Search Console, que desglosa cada tipo de recurso por rastreador.

En un log de Apache o Nginx, esto saca lo que Googlebot ha pedido para renderizar:

```
# Peticiones de Googlebot a JS, CSS y API, con su código de respuesta
grep "Googlebot" access.log | grep -E '\.js|\.css|/api/' | awk '{print $9, $7}' | sort | uniq -c | sort -rn
```

Cualquiera puede llamarse Googlebot en el user-agent, así que comprueba la IP. Un Googlebot de verdad resuelve a un dominio de Google, y ese dominio vuelve a la misma IP:

```
host 66.249.66.1
# 1.66.249.66.in-addr.arpa domain name pointer crawl-66-249-66-1.googlebot.com.
host crawl-66-249-66-1.googlebot.com
# crawl-66-249-66-1.googlebot.com has address 66.249.66.1
```

Ahí ves si un recurso crítico devuelve error, si está bloqueado o si Google se gasta el rastreo en ficheros que cambian de URL en cada deploy. Dos avisos para leerlos bien: los logs te dicen qué se pidió, no qué se renderizó ni cuándo; y que un recurso no aparezca no significa que no se usase, porque puede venir de la caché. Las peticiones de Google-InspectionTool, por cierto, son tus propias pruebas.

## Fuera de Google: Bing, los chats de IA, sus rastreadores y los navegadores con IA

Que Google ejecute tu JavaScript no quiere decir que lo haga nadie más, y la respuesta cambia según quién visite la página.

Esto cambia rápido, así que aquí todo lleva fecha. Si lo lees unos meses después, repite la prueba antes de fiarte (de mí también).

### Bing: dice que renderiza, pero mis pruebas dicen otra cosa

Con Bingbot he usado el mismo contador de JavaScript que con Google. Si el rastreador ejecuta el script, el contador aparece modificado en el DOM que guarda. Para mi sorpresa (en realidad no), los cambios de JavaScript no aparecieron en lo que Bing tenía guardado, y obtuve lo mismo en otros proyectos.

![El contador de JavaScript instalado en mi web falla en el crawler de Bing](https://seomanal.es/uploads/articulos/2-d60a8f2a697a6d38.webp)
*El contador de JS tal como lo devolvía Bing Webmaster Tools el 27 de noviembre de 2025: las dos fechas son idénticas y el contador está a 0. El script no se ejecutó.*

La segunda pista está en los informes. En sitios hechos en CSR, Bing Webmaster Tools te dice que faltan la canonical, la meta description y el h1 cuando sí existen. Son falsos negativos: están puestos por JavaScript y en lo que Bing tiene guardado no salen. En la prueba en vivo de la misma herramienta, en cambio, sí aparecen:

![Arriba, lo que Bing tiene en su sistema, sin renderizar JavaScript; abajo, la prueba en vivo de Bing, que sí lo renderiza](https://seomanal.es/uploads/articulos/2-4031e4e59fe7f6fd.webp)
*Home de un sitio en CSR: arriba lo que Bing tiene en su sistema, abajo la prueba en vivo.*

Bing, por tanto, **es capaz de renderizar cuando se lo pides en vivo**. Lo que no veo es que lo haga en lo que usa para indexar. Mi opinión es que Bing no renderiza JavaScript, o al menos no de forma tan completa como Google, y lo he visto también en proyectos enormes con buenas métricas en Bing. La prueba es de noviembre de 2025 y la tengo pendiente de repetir, porque desde entonces Bing ha cambiado sus directrices.

Hasta principios de 2026, esas directrices recomendaban el renderizado dinámico para Bingbot, sobre todo en sitios grandes (sospechoso: si de verdad renderizan, ¿para qué un respaldo prerrenderizado?). En febrero de 2026 Bing reescribió la página para hablar de Copilot y de grounding, las fuentes en las que se apoya una respuesta de IA, y ese párrafo desapareció. Según la versión que leí entonces ([esta URL](https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a), que al cerrar esta guía, en octubre de 2026, devolvía una página de error), Bing pide que dejes a Bingbot rastrear y renderizar de forma eficiente, que no escondas contenido crítico tras el renderizado en cliente y que no le obligues a hacer peticiones de más. Y avisa: lo que no se pueda renderizar con fiabilidad puede no indexarse ni elegirse para las respuestas de IA.

No es una prohibición del renderizado dinámico: Bing [lo aceptaba](https://blogs.bing.com/webmaster/october-2018/bingbot-Series-JavaScript,-Dynamic-Rendering,-and-Cloaking-Oh-My) si el contenido es el mismo para todos, y [otra documentación de Microsoft](https://learn.microsoft.com/en-us/microsoft-copilot-studio/guidance/generative-ai-public-websites) todavía lo recomienda. Yo lo leo así: Bing sigue diciendo que renderiza, pero ya te pide que lo importante no dependa del cliente. Un detalle con gracia: esa página de directrices está hecha en CSR, y sin JavaScript devuelve un contenedor vacío.

### Cuatro formas en que una IA visita tu web

Cuando alguien pregunta si ChatGPT o Claude «renderizan JavaScript», la respuesta depende de qué parte del producto esté visitando la página:

| Quién visita | Qué hace | ¿Ejecuta JavaScript? |
| --- | --- | --- |
| El chat, cuando le das una URL | Pide la página en ese momento para contestarte | Casi nunca: lee el HTML que devuelve el servidor |
| Los rastreadores de fondo (GPTBot, ClaudeBot, PerplexityBot…) | Recorren la web para entrenar modelos o alimentar un índice propio | No. Descargan los ficheros JS, pero no los ejecutan |
| El índice de Google (AI Overviews, AI Mode) | Recupera las páginas del índice de Google | No en el momento: usa lo que Google ya indexó, que es el HTML renderizado (es una deducción; Google no lo dice con esas palabras) |
| Un navegador o un agente con IA (el navegador de Claude Code, Claude en Chrome, Comet de Perplexity, el agente de ChatGPT, Google-Agent…) | Abre la página en un navegador de verdad y actúa sobre ella | Sí: es un navegador, con su JavaScript y su CSS |

La cuarta es la que más se olvida, y explica por qué un mismo producto puede darte dos respuestas distintas sobre la misma página. Google tiene desde marzo de 2026 un fetcher documentado para esto, [Google-Agent](https://developers.google.com/search/docs/crawling-indexing/google-user-triggered-fetchers), que usan «los agentes alojados en la infraestructura de Google para navegar por la web y actuar a petición del usuario», con rangos de IP propios en `user-triggered-agents.json`. ChatGPT Atlas, el navegador de OpenAI, estaría en esta fila si siguiera existiendo: OpenAI anunció su retirada el 9 de julio de 2026 y [dejó de funcionar el 9 de agosto](https://help.openai.com/en/articles/6825453-chatgpt-release-notes), con la promesa de llevar esa navegación con agente a ChatGPT.

### Los chats de IA: la prueba del código de descuento

El punto de partida fue [un experimento de Andre Alpar](https://www.searchengineworld.com/do-ai-assistants-actually-render-your-javascript-when-grounding-we-put-it-to-the-test) (Search Engine World, 9 de junio de 2026, actualizado el 2 de julio), que publicó una tabla con los chats de IA que ejecutaban JavaScript y los que no. Su montaje era más completo que el mío: un dato señuelo en el HTML, el dato real cargado por JavaScript desde otro endpoint y los logs de las tres peticiones.

![Tabla del experimento de Andre Alpar sobre qué chats de IA renderizan JavaScript](https://seomanal.es/uploads/articulos/2-3f1a7fa24911cd52.webp)
*Tabla del experimento de Andre Alpar. El material es suyo, no mío.*

Mi naturaleza curiosa me impide tener una fe ciega, así que [lo repliqué por mi cuenta](https://sanchezdonate.net/articulo/aio-o-geo-el-seo-enfocado-en-llms/#javascript). En la web de [Andalu-SEO](https://andalu-seo.es/) puse un código de descuento cuyo texto solo se carga por JavaScript: en el HTML del servidor no está. Le das la URL al chat y le preguntas si hay algún descuento para el año que viene. Si te contesta con el código, ha ejecutado el script. Si no, se ha quedado con el HTML del servidor.

Para que fuese justo cuidé tres cosas:

- Forcé un cambio de caché y lancé la pregunta en todos los chats a la vez, para que ninguno tuviera ventaja.
- Medí cuánto tarda en aparecer el dato, para descartar que alguno hiciera su captura antes de que cargase el script. Desde España son unos 300 ms.
- Como Andalu-SEO no tiene CDN, repetí la medición con una VPN desde Australia, por si algún servidor de IA lo gestionaba desde allí.

![Consola del navegador con el tiempo que tarda en inyectarse el dato por JavaScript, medido desde España: unos 300 ms](https://seomanal.es/uploads/articulos/2-5a7e02ece6ab5fef.webp)
*Medición desde España: el dato está inyectado a los 300 ms.*

![VPN conectada en Melbourne y consola del navegador con el tiempo de inyección del dato medido desde Australia](https://seomanal.es/uploads/articulos/2-04df1c28d0b0faa7.webp)
*La misma medición con la VPN conectada en Melbourne.*

Estos son los resultados a **25 de junio de 2026**, tras 5 pruebas (la tabla la mantengo al día en [mi artículo sobre GEO, el SEO para modelos de lenguaje](https://sanchezdonate.net/articulo/aio-o-geo-el-seo-enfocado-en-llms/#que-inteligencias-artificiales-renderizan-javascript)):

| Chat | Análisis de Andre Alpar | Mis pruebas (a 25 de junio de 2026) |
| --- | --- | --- |
| Qwen | Renderiza JS | Renderiza JS |
| DeepSeek | Renderiza JS | A veces |
| Kimi | Renderiza JS | A veces |
| Mistral | Renderiza JS | A veces |
| Grok | Ejecuta JS pero ignora el resultado | No renderiza |
| ChatGPT | No renderiza | No renderiza |
| Claude | No renderiza | No renderiza |
| Gemini | No renderiza | No renderiza |
| Perplexity | No renderiza | No renderiza |
| Copilot | No renderiza | No renderiza |

En los grandes coincidimos: ChatGPT, Claude, Gemini, Perplexity y Copilot no vieron el código, y Qwen sí. Las diferencias están en el resto. DeepSeek y Kimi lo vieron unas veces y otras no, la renderización de Schrödinger. Mistral sale como «a veces», aunque en las capturas que tengo publicadas no fue capaz: tómalo como un «no cuentes con ello». Y Perplexity me dio esperanzas al ofrecerse a verificarlo, pero no.

La prueba mide algo muy concreto: qué ve el chat cuando le das una URL y tiene que leerla en ese momento, la primera fila de la tabla de las cuatro formas. No mide el rastreo de fondo con el que estas empresas entrenan sus modelos o alimentan sus índices. Tampoco tengo publicados los logs del servidor, así que no puedo demostrar si cada chat llegó a pedir la URL o contestó con lo que ya tenía. Lo que sí sé es que el método detecta el renderizado cuando lo hay, porque Qwen vio el dato. Son 5 pruebas, y las seguiré repitiendo.

### Un chat no es un navegador

La prueba del descuento tampoco mide los navegadores y agentes con IA, y ahí el resultado es el contrario. Un chat que «lee una URL» suele hacer una petición HTTP, recibir el HTML y pasárselo al modelo convertido en texto: si no hay un navegador de por medio, nadie ejecuta el JavaScript. Un navegador con IA abre la página en un navegador de verdad, con su JavaScript y su CSS, y lo que le llega al modelo es el resultado.

La diferencia se ve dentro de un mismo producto. Claude Code tiene dos formas de abrir una web, y el 4 de octubre de 2026 la página de Andalu-SEO pasó por las dos:

- Su herramienta de lectura de URL, la que funciona como un chat, respondió que la página no menciona ningún descuento para 2027 y enumeró los códigos de los hoteles, que sí están en el HTML del servidor.
- Su navegador integrado, un Chromium, cargó la página, y el bloque con el código de descuento estaba en el DOM.

«Claude no renderiza JavaScript» es verdad para el chat y deja de serlo cuando Claude maneja un navegador, y lo mismo vale para cualquier producto que tenga las dos cosas.

Para estos agentes, [Google describe](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide) tres formas de leer una web: mirar capturas de pantalla, recorrer el DOM e interpretar el árbol de accesibilidad. Ahí ya no basta con que el texto esté en el DOM: también cuenta cómo se pinta y cómo lo describe el HTML, y por eso recomienda un HTML semántico y remite a [la guía de web.dev sobre sitios preparados para agentes](https://web.dev/articles/ai-agent-site-ux).

No conviene sacar de aquí la conclusión equivocada. Un agente con navegador va a donde lo manda su usuario. Para llegar a ser la página a la que lo mandan, o la que cita un chat, sigues dependiendo de lo que leen los rastreadores y los chats, que es el HTML del servidor. Y solo he probado un navegador.

### Los rastreadores de IA: descargan el JavaScript, pero no lo ejecutan

Para el rastreo de fondo, el mejor dato público que conozco es [el estudio de Vercel y MERJ sobre los rastreadores de IA](https://vercel.com/blog/the-rise-of-the-ai-crawler), de diciembre de 2024, hecho con los logs de la red de Vercel. Ninguno de los grandes ejecutaba JavaScript: ni los de OpenAI (GPTBot, OAI-SearchBot, ChatGPT-User), ni ClaudeBot, ni Meta-ExternalAgent, ni Bytespider, ni PerplexityBot, ni el CCBot de Common Crawl. La excepción era AppleBot, que rastrea con un navegador.

Los ficheros JS sí los descargan: en nextjs.org eran el 11,5 % de las peticiones de ChatGPT y el 23,8 % de las de Claude. Y rastrean bastante peor que Google: en torno a un 34 % de sus peticiones acababa en un 404, frente al 8 % de Googlebot en el mismo sitio. Es una foto de un solo sitio y de hace casi dos años. El motivo, en mi opinión, es económico: rastrear con JavaScript es mucho más caro, y consiguen lo que quieren sin hacerlo.

Puedes ver tu página como la recibe uno de estos rastreadores pidiéndola sin ejecutar JavaScript y con su user-agent (la cadena oficial de GPTBot, con su versión, está en la [documentación de OpenAI](https://platform.openai.com/docs/bots) y cambia de vez en cuando):

```
curl -s -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot" \
  https://ejemplo.com/pagina/ | grep -c "una frase de tu contenido principal"
```

Si devuelve 0, ese texto no existe para ellos.

### Gemini, AI Overviews y AI Mode: la vía del índice de Google

Con Google hay dos preguntas distintas. La primera: ¿renderiza el rastreo que alimenta a Gemini? Google no lo ha dicho con esas palabras. Vercel y MERJ dedujeron de sus logs, en [su estudio de diciembre de 2024](https://vercel.com/blog/the-rise-of-the-ai-crawler), que Gemini «se apoya en la infraestructura de Googlebot» y que eso le da el mismo renderizado que la Búsqueda. Es una inferencia suya, no una declaración de Google. La segunda: ¿ejecuta Gemini el JavaScript cuando le das una URL en el chat? En mis pruebas no vio el dato. No es una contradicción: una cosa es lo que Google ya ha rastreado y renderizado con calma, y otra la lectura en vivo desde el chat, que puede ser una petición simple o tirar de una copia anterior. Esto último es una hipótesis; sin logs no puedo demostrarla.

AI Overviews y AI Mode tiran del índice de Google. Para salir como enlace, [basta con que la página esté indexada](https://developers.google.com/search/docs/appearance/ai-features) y pueda mostrarse con fragmento, sin requisitos técnicos adicionales, y Google procesa el contenido que depende de JavaScript mientras no esté bloqueado. Desde el 31 de agosto de 2026 hay una condición más, y no es técnica: la propiedad no puede estar excluida de las funciones de IA generativa en Search Console. Ese [control](https://support.google.com/webmasters/answer/16908024) viene activado por defecto (incluir), se hereda de la propiedad superior, cubre AI Overviews, AI Mode y las funciones generativas de Discover, y no afecta ni al ranking ni al entrenamiento de modelos; la [guía de Google para funciones de IA generativa](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide) ya lo lista entre los requisitos. Si Google ya ha renderizado e indexado tu CSR, es razonable esperar que sus funciones de IA vean ese contenido igual que la búsqueda clásica. Para la lectura en vivo y para el resto de chats, no cuentes con ello.

### Qué hacer: el contenido importante, en el HTML del servidor

La regla sale sola: **lo que quieras que lean las IA tiene que estar en el HTML que devuelve el servidor**. Contenido principal, title, metadatos y navegación. Da igual que sea SSR, estático o ISR. Si tu foco es Google, el CSR puede valer; para posicionar en LLM no es una buena opción, y con Bing pierdes oportunidades.

¿Y si no puedes tocar la arquitectura? El renderizado dinámico solo para los user-agents de los LLM queda como apaño, con dos cautelas: Google lo desaconseja, y depende de reconocer el user-agent, cuando no todos los LLM se identifican (Grok se presenta con el de un iPhone y Copilot no tiene uno propio).

Sobre el llms.txt: en Google no recibe ningún tratamiento especial. [La documentación lo dice desde el 15 de junio de 2026](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide): la Búsqueda lo ignora, ni ayuda ni perjudica, y mantenerlo para otros servicios que sí lo usen es cosa tuya. Martin Splitt lo repitió en Andalu-SEO 2026, y yo ya había escrito que ni el llms.txt ni el ai.txt funcionan.

Para comprobarlo basta el código fuente, sin ejecutar JavaScript. Si el dato no está ahí, hoy la mayoría de los chats y casi todos los rastreadores de IA no lo van a ver.

## Checklist para auditar el renderizado de tu web

Aquí no hay teoría nueva: es la guía entera en forma de lista, en el orden en el que yo audito una web. Cada punto enlaza al capítulo que lo explica.

### El HTML que sale del servidor

- La URL devuelve un 200 real si existe y un 404 real si no ([códigos de estado](#soft-404)).
- En una SPA, las páginas de error redirigen a una URL que responde 404 desde el servidor o reciben un noindex por JS.
- El HTML inicial no lleva noindex si quieres que la página se indexe: quitarlo después con JavaScript no funciona ([noindex en origen](#noindex-origen)).
- La canonical sale correcta del servidor (a sí misma, o ninguna), solo hay una y ningún script la cambia ([la canonical](#canonical)).
- El meta robots y la canonical no cambian durante la carga. Si Search Console marca noindex y la prueba en vivo dice index, pasa el fragmento del [flashazo](#flashazo).
- El title, las metaetiquetas, la canonical y los datos estructurados esenciales van arriba y dentro de los primeros 2 MB de HTML sin comprimir, cabeceras incluidas; el CSS y el JS pesados, a ficheros externos ([el límite de 2 MB](#limite-2mb)).
- El `<head>` solo contiene etiquetas válidas.

### El contenido y los enlaces

- El contenido principal está en el HTML del servidor. Es la única forma de que lo lean Google, Bing y las IA a la vez.
- Nada importante depende de un clic, de un scroll ni de aceptar un permiso ([sin clics](#sin-clics)).
- Nada importante depende de un temporizador de más de unos 5 segundos ([el misterio de los 5 segundos](#reloj-virtual)).
- Una API lenta no es el fin del mundo, pero no apures: el corte observado en la indexación real ronda los 30 segundos, y es variable.
- La carga diferida se dispara por visibilidad (`loading="lazy"` o IntersectionObserver), no por el evento scroll ([carga diferida](#lazy)).
- Los enlaces son `<a href>` con URL reales, y las vistas de una SPA usan la History API en lugar de fragmentos con `#`.
- Los enlaces importantes están en el HTML inicial o, como mínimo, en un sitemap al día ([descubrir los enlaces](#enlaces-js)).

### CSS y ventana

- Todo bloque con `100vh` o `dvh` lleva un `max-height` razonable (yo suelo poner 800 px). Cuesta una línea y te ahorra el debate ([el 100vh](#cien-vh)).
- No hay texto relevante dentro de `::before` o `::after` ([pseudoelementos](#pseudoelementos)).
- Si la captura de Search Console se corta a 1750 px, no te asustes: es el límite del visor ([el corte](#corte-1750)).

### Recursos, caché y robots.txt

- El robots.txt no bloquea ni el JS, ni el CSS, ni las API que pintan el contenido ([recursos bloqueados](#bloqueados)).
- Los ficheros llevan una huella de contenido en el nombre, que cambia solo cuando cambia el fichero ([la caché](#cache-30-dias)).
- El JS y el CSS críticos se sirven desde el mismo hostname; a otro host, solo vídeos o descargas.
- Ningún fichero necesario pasa de 2 MB sin comprimir.
- Nada depende de cookies, de `localStorage` ni de `sessionStorage`, ni de WebSockets, WebRTC o web workers.

### Cómo comprobarlo, paso a paso

1. Pide la URL sin JavaScript (`curl` o Ctrl+U) y mira el código de estado, el meta robots y la canonical.
2. Abre la inspección de URL en Search Console y mira el HTML de la página rastreada. Eso es lo que Google tiene de verdad. Si el contenido está ahí, no tienes un problema de JavaScript ([la inspección de URL](#inspeccion)).
3. Usa «Probar URL publicada» para depurar: recursos que no cargan, errores de consola. No para la comprobación final, porque no tira de la caché de Google.
4. Si la web no es tuya, usa la prueba de resultados enriquecidos.
5. Rastrea con Screaming Frog con el renderizado de JavaScript activado y compara el HTML inicial con el renderizado. Todo lo que solo exista en el renderizado es lo que dejas en manos del JS.
6. Revisa los logs: qué recursos pide Googlebot y si tu servidor responde lento.

### Bing y las IA

- En mis pruebas Bing no refleja en su índice los cambios hechos por JS, y sus directrices piden no esconder contenido crítico tras el renderizado en cliente.
- La mayoría de los chats de IA leen el HTML sin ejecutar JS. Mira la fecha de la última prueba antes de decidir nada con ella.
- Un navegador con IA sí ejecuta tu JavaScript, pero llega cuando un usuario lo manda. No lo uses como excusa para dejar el contenido fuera del HTML.
- Si el contenido, el title, la canonical o el h1 no están en el HTML sin JavaScript, dalos por no vistos por Bing y por la mayoría de las IA ([qué hacer](#que-hacer-ia)).

### Qué estrategia elegir, en dos preguntas

- ¿Tu web vive de que encuentren su contenido (blog, medio, ecommerce, fichas)? Si cambia poco, estático. Si cambia en cada petición, SSR. Si es grande y cambia a menudo, ISR.
- ¿Es una aplicación interactiva (un panel, una herramienta, algo tras un login)? CSR, asumiendo el coste: enlaces que solo existen tras renderizar, más fragilidad y Bing y las IA a ciegas. Lo poco que quieras posicionar de esa aplicación, en el HTML del servidor ([estrategias](#estrategias)).

## Preguntas abiertas

Hay cosas de esta guía que no están cerradas:

- Que el reloj del navegador de Google no es el nuestro y que el 5 de los contadores son segundos suyos se ha medido en varias pruebas y encaja con lo que Google ha contado, pero no lo tiene documentado.
- Nadie sabe cuánto espera de verdad el 10 % de páginas más lento de la cola de renderizado.
- No hay un experimento publicado que demuestre que un hero a 100vh perjudique la indexación. Yo lo he visto mejorar al corregirlo, pero eso es experiencia.
- Tampoco está documentado que una canonical hacia otra URL corte el renderizado, ni si Google usa los enlaces de una página noindex que llega a renderizar.

Y casi todas las cifras de la guía (los 5 segundos del reloj de Google, los 1742 px de la ventana, los 30 segundos de espera máxima) son observaciones de pruebas, no documentación: pueden cambiar mañana.

Tengo pendiente repetir la prueba de Bing con sus directrices nuevas, volver a pasar la de los chats de IA guardando los logs del servidor, probar más navegadores con IA y publicar un caso de 100vh con datos de antes y después. Según lo vaya haciendo, actualizaré esta guía con la fecha.

Algunas cosas de las que cuento aquí corrigen lo que yo mismo había publicado: el corte a los 5 segundos, el scroll de Googlebot, los «3 rastreos» del CSR y que los LLM nunca ejecutan JavaScript. Si en un artículo antiguo de mi blog lees otra cosa, vale lo que pone aquí.

La comprobación, mientras tanto, es la misma de siempre: inspección de URL en Search Console, «Ver página rastreada», y comparar ese HTML con lo que tú crees que sirves.

## Referencias

1. Andalu-SEO (s. f.). *Rendering: What, how, why*. andalu-seo.es. <https://andalu-seo.es/ponente/2026/martin-splitt>
2. andalu-seo.es (s. f.). *Andalu-SEO | Evento de SEO Avanzado*. <https://andalu-seo.es/> (La página de la prueba: el código de descuento que solo se carga por JavaScript)
3. Ayuda de Google (s. f.). *URL Inspection tool - Search Console Help*. <https://support.google.com/webmasters/answer/9012289?hl=en>
4. Bing Webmaster Blog (2018, 31 de octubre). *bingbot Series: JavaScript, Dynamic Rendering, and Cloaking. Oh My!*. <https://blogs.bing.com/webmaster/october-2018/bingbot-Series-JavaScript,-Dynamic-Rendering,-and-Cloaking-Oh-My>
5. bing.com (s. f.). *Bing Webmaster Guidelines*. <https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a>
6. Bućko, Z. (2022, 9 de noviembre). *Rendering Queue: Google Needs 9X More Time To Crawl JS Than HTML*. onely.com. <https://www.onely.com/blog/google-needs-9x-more-time-to-crawl-js-than-html/>
7. Costello, R. (2019, 22 de julio). *How JavaScript Rendering Works*. lumar.io. <https://www.lumar.io/ebooks/javascript-seo-guide/how-javascript-rendering-works/>
8. Duda (2023, 22 de agosto). *Exploring the Art of Rendering with Google's Martin Splitt*. youtube.com. <https://www.youtube.com/watch?v=-newLrArh6c>
9. Google Search Central (2019, 7 de mayo). *The new evergreen Googlebot*. <https://developers.google.com/search/blog/2019/05/the-new-evergreen-googlebot>
10. Google Search Central (s. f.). *AI features and your website*. <https://developers.google.com/search/docs/appearance/ai-features>
11. Google Search Central (s. f.). *Dynamic Rendering as a Workaround*. <https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering>
12. Google Search Central (s. f.). *Fix Lazy-Loaded Website Content*. <https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading>
13. Google Search Central (s. f.). *Fix Search-related JavaScript problems*. <https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript>
14. Google Search Central (s. f.). *Generate Structured Data with JavaScript*. <https://developers.google.com/search/docs/appearance/structured-data/generate-structured-data-with-javascript>
15. Google Search Central (s. f.). *Google's common crawlers*. <https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers>
16. Google Search Central (s. f.). *Optimizing your website for generative AI features on Google Search*. <https://developers.google.com/search/docs/fundamentals/ai-optimization-guide>
17. Google Search Central (s. f.). *Pagination Best Practices for Google*. <https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading>
18. Google Search Central (s. f.). *Understand the JavaScript SEO basics*. <https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics>
19. Illyes, G. (2026, 31 de marzo). *Inside Googlebot: demystifying crawling, fetching, and the bytes we process*. Google Search Central. <https://developers.google.com/search/blog/2026/03/crawler-blog-post>
20. learn.microsoft.com (s. f.). *Use public websites to improve generative answers (Microsoft Copilot Studio)*. <https://learn.microsoft.com/en-us/microsoft-copilot-studio/guidance/generative-ai-public-websites>
21. Miller, J. (2020, 11 de agosto). *Islands Architecture*. jasonformat.com. <https://jasonformat.com/islands-architecture/>
22. nextjs.org (s. f.). *Streaming (documentación de Next.js)*. <https://nextjs.org/docs/app/guides/streaming>
23. nextjs.org (s. f.). *generateMetadata (documentación de Next.js)*. <https://nextjs.org/docs/app/api-reference/functions/generate-metadata>
24. nextjs.org (s. f.). *htmlLimitedBots (documentación de Next.js)*. <https://nextjs.org/docs/app/api-reference/config/next-config-js/htmlLimitedBots>
25. Osmani, A. y Miller, J. (2019, 6 de febrero). *Rendering on the Web*. web.dev. <https://web.dev/articles/rendering-on-the-web>
26. react.dev (s. f.). *renderToPipeableStream (documentación de React)*. <https://react.dev/reference/react-dom/server/renderToPipeableStream>
27. Rudzki, T. (2019, 22 de noviembre). *JavaScript Indexing Delays Are Still an Issue for Google*. Search Engine Journal. <https://www.searchenginejournal.com/javascript-indexing-delays-google/335703/>
28. Sánchez, C. (2023, 10 de julio). *Renderizado en el SEO*. sanchezdonate.net. <https://sanchezdonate.net/articulo/renderizado-en-el-seo/>
29. Sánchez, C. (2023, 16 de marzo). *Renderings de JavaScript en el SEO*. sanchezdonate.net. <https://sanchezdonate.net/articulo/renderizacion-de-javascript-en-el-seo/>
30. Sánchez, C. (2023, 19 de junio). *Renderizado en el SEO – Ponencia de Martin Splitt*. sanchezdonate.net. <https://sanchezdonate.net/articulo/noticia/martin-splitt-renderizacion/>
31. Sánchez, C. (2024, 17 de marzo). *El VH de CSS afecta a la renderización*. sanchezdonate.net. <https://sanchezdonate.net/articulo/vh-css-rendering/>
32. Sánchez, C. (2024, 19 de enero). *Diferencias entre Java y JavaScript. No son lo mismo*. sanchezdonate.net. <https://sanchezdonate.net/articulo/curiosidades/java-javascript-seo/>
33. Sánchez, C. (2025, 1 de diciembre). *Advanced SEO Guide to Rendering: How to Debug, Test & Control What Google Sees*. sitebulb.com. <https://sitebulb.com/resources/guides/advanced-seo-guide-to-rendering-how-to-debug-test-control-what-google-sees/>
34. Sánchez, C. (2025, 11 de noviembre). *Guía avanzada sobre el renderizado*. sanchezdonate.net. <https://sanchezdonate.net/articulo/guia-avanzada-sobre-el-renderizado/>
35. Sánchez, C. (2025, 12 de agosto). *AIO o GEO. EL SEO enfocado en LLMs*. sanchezdonate.net. <https://sanchezdonate.net/articulo/aio-o-geo-el-seo-enfocado-en-llms/#javascript>
36. Sánchez, C. (2025, 17 de junio). *Donde corta el renderizado de Search Console*. sanchezdonate.net. <https://sanchezdonate.net/articulo/curiosidades/donde-corta-el-renderizado-de-search-console/>
37. Sánchez, C. (2025, 22 de enero). *JS para mejorar tu contenido*. sanchezdonate.net. <https://sanchezdonate.net/articulo/js-para-mejorar-tu-contenido/>
38. Sánchez, C. (2025, 4 de noviembre). *Calcular tiempo de renderizado de Google*. sanchezdonate.net. <https://sanchezdonate.net/articulo/calcular-tiempo-de-renderizado-de-google/>
39. Sánchez, C. (2025, 7 de marzo). *Optimización avanzada con buffer de PHP: modificando la salida HTML antes del envío*. sanchezdonate.net. <https://sanchezdonate.net/articulo/buffer-php-modificar-html/>
40. Sánchez, C. (2025, 9 de noviembre). *Renderizado. Revisar flashazos de noindex*. sanchezdonate.net. <https://sanchezdonate.net/articulo/curiosidades/revisar-flashazos-noindex/>
41. Schwartz, B. (2017, 28 de diciembre). *Google: Long Term Noindex Will Lead To Nofollow Also*. Search Engine Roundtable. <https://www.seroundtable.com/google-long-term-noindex-follow-24990.html>
42. Schwartz, B. (2018, 18 de enero). *GoogleBot Can Now Crawl Mobile 12,140 Pixels High & Desktop 9,307 Pixels High*. Search Engine Roundtable. <https://www.seroundtable.com/googlebot-taller-pixels-25098.html>
43. Schwartz, B. (2020, 27 de marzo). *Google Says There Is No Such Thing As Two Waves Of Indexing Or Crawling*. Search Engine Roundtable. <https://www.seroundtable.com/google-no-two-waves-indexing-29225.html>
44. Schwartz, B. (2026, 26 de febrero). *Bing Webmaster Guidelines Updated*. Search Engine Roundtable. <https://www.seroundtable.com/bing-webmaster-guidelines-updated-41002.html>
45. Screaming Frog (2017, 5 de enero). *How To Crawl JavaScript Websites*. <https://www.screamingfrog.co.uk/seo-spider/tutorials/crawl-javascript-seo/>
46. Siddle, R. (2021, 12 de julio). *12 Experiments for Tabbed Content SEO*. merj.com. <https://merj.com/blog/12-experiments-for-tabbed-content-seo>
47. Smart, D. (2021, 11 de septiembre). *Web Worker Content - Will It Index?*. tamethebots.com. <https://tamethebots.com/blog-n-bits/will-web-worker-contents-index>
48. Smart, D. (2025, 24 de junio). *Noindex No Longer Means Not Rendered*. tamethebots.com. <https://tamethebots.com/blog-n-bits/noindex-does-not-mean-not-rendered>
49. Smart, D. (2026, 23 de julio). *Google doesn't cut off rendering after 5 seconds, but I understand why you might think it does.*. tamethebots.com. <https://tamethebots.com/blog-n-bits/the-5-second-google-render-limit-myth>
50. Southern, M. G. (2025, 2 de mayo). *Server-Side vs. Client-Side Rendering: What Google Recommends*. Search Engine Journal. <https://www.searchenginejournal.com/server-side-vs-client-side-rendering-what-google-recommends/545946/>
51. Southern, M. G. (2026, 27 de febrero). *Bing Adds GEO To Official Guidelines, Expands AI Abuse Definitions*. Search Engine Journal. <https://www.searchenginejournal.com/bing-adds-geo-to-official-guidelines-expands-ai-abuse-definitions/568442/>
52. Splitt, M. (2019, 12 de noviembre). *How to make your content shine on Google Search (Chrome Dev Summit 2019)*. youtube.com. <https://www.youtube.com/watch?v=4pOH8Smd0Xs>
53. Splitt, M. (2023, 18 de junio). *What rendering really means for SEO - Martin Splitt #SOB23*. youtube.com. <https://www.youtube.com/watch?v=BU8ZvTq4A6M>
54. Splitt, M. (s. f.). *Rendering: What, how, why (diapositivas de la ponencia en Andalu-SEO 2026)*. andalu-seo.es. <https://andalu-seo.es/ponente/2026/pdf/04-rendering-golden-circle-martin-splitt.pdf>
55. Splitt, M. y Illyes, G. (2024, 3 de diciembre). *Crawling December: The how and why of Googlebot crawling*. Google Search Central. <https://developers.google.com/search/blog/2024/12/crawling-december-resources>
56. Splitt, M. y Mueller, J. (2025, 24 de julio). *How does CSS affect SEO? (Search Off the Record, episodio 96)*. youtube.com. <https://www.youtube.com/watch?v=l2E3gBDP_qY>
57. Suzuki, K. (2025, 1 de mayo). *Entrevista de Kenichi Suzuki a Martin Splitt sobre datos estructurados y JavaScript*. youtube.com. <https://www.youtube.com/watch?v=jHYUD0x44uM>
58. Tony (2025, 5 de septiembre). *Tony's Theory of Googlebot Relativity*. bigcommerce.websiteadvantage.com.au. <https://bigcommerce.websiteadvantage.com.au/tonys-theory-of-googlebot-relativity/>
59. web.dev (s. f.). *Build agent-friendly websites*. <https://web.dev/articles/ai-agent-site-ux>
60. Zarr, O. (2024, 30 de diciembre). *JavaScript SEO Guide for SEOs and Developers*. seosly.com. <https://seosly.com/blog/javascript-seo/>
61. Zarr, O. (2024, 6 de mayo). *JavaScript SEO in 2024 Q&A with Martin Splitt from Google (Issues, Mistakes, Tips, Facts, & Myths)*. youtube.com. <https://www.youtube.com/watch?v=0iHDk7uqByI>
62. Zecchini, G., Moore, A. A., Ubl, M. y Siddle, R. (2024, 17 de diciembre). *The rise of the AI crawler*. vercel.com. <https://vercel.com/blog/the-rise-of-the-ai-crawler>
63. Zecchini, G., Moore, A. A., Ubl, M. y Siddle, R. (2024, 31 de julio). *How Google handles JavaScript throughout the indexing process*. vercel.com. <https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process>
