JavaScript SEO: cómo indexa Google las SPA y los sitios en React
La cola de renderizado en dos oleadas de Google, las trampas del CSR, la diferencia entre SSR, SSG e ISR y métodos prácticos para comprobar que tu sitio con JavaScript se indexa correctamente.
Cómo renderiza Google JavaScript: cómo funciona
En una página HTML normal, Google solo da un paso: descarga el HTML y ve el contenido de inmediato. En una página con JavaScript hay tres pasos: descargar el HTML, cargar el paquete JS y ejecutarlo en Chromium sin interfaz gráfica para obtener el DOM final.
Google usa su propio motor de navegador basado en Chromium, que se actualiza con regularidad. Por eso Googlebot entiende las funciones modernas de JavaScript (ES2020+, import dinámico, WebAssembly). El problema no es el soporte de sintaxis, sino el retraso del renderizado y las limitaciones de recursos durante el rastreo.
Renderizado en cifras
Tiempo límite de renderizado
Si el JS no se ejecuta en unos 3–4 segundos, Google indexa una página renderizada solo en parte
Tiempo de CSS
Los recursos que tardan más de 150 ms en cargarse pueden bloquear el renderizado y perjudicar la eficiencia del rastreo
Indexación
El contenido JS llega al índice con retraso respecto al contenido HTML: a veces, días o semanas
Google siempre actualizado
Googlebot se actualiza a la última versión estable de Chromium: las funciones modernas de JS son totalmente compatibles
Indexación en dos oleadas: por qué el contenido JS llega más tarde al índice
Google divide el proceso en dos etapas. La primera oleada es el rastreo: Googlebot descarga el HTML y todos los enlaces que descubre. La segunda oleada es el renderizado: el bot pone las páginas en cola para renderizarlas, ejecuta el JS e indexa el DOM final.
Entre una oleada y otra pueden pasar desde horas hasta semanas, según la autoridad del sitio, el presupuesto de rastreo y el tamaño de la cola de renderizado de Google. Si tus textos clave, los H1, las metaetiquetas o los enlaces se generan solo con JS, llegarán al índice con retraso.
CSR: la principal trampa para el SEO
El renderizado en el cliente (CSR) consiste en que el servidor envía un <div id="root"></div> vacío y todo el HTML se construye en el navegador tras cargar el paquete JS. React, Vue y Angular en modo SPA funcionan así por defecto.
Desde el punto de vista del SEO, el CSR es un problema por varias razones: 1) el renderizado solo ocurre en la segunda oleada; 2) los recursos que el rastreador tiene bloqueados (scripts de terceros, CDN bloqueadas en robots.txt) no se cargan y la página se renderiza de forma incompleta; 3) cualquier error de JS deja la página en blanco en el índice.
<!-- CSR: what Google sees in the first wave -->
<html>
<head>
<title></title> <!-- empty -->
</head>
<body>
<div id="root"></div> <!-- no content -->
<script src="/bundle.js"></script>
</body>
</html><title>, <meta name="description"> y <h1> solo con JavaScript en el cliente. Google recibe valores vacíos o por defecto durante el primer rastreo e indexa la página sin ellos.SSR, SSG e ISR: cuál elegir para el SEO
La solución a los problemas del CSR es generar el HTML en el servidor. Hay tres enfoques principales, y cada uno es óptimo para su escenario.
Comparativa de enfoques de renderizado
| Enfoque | HTML en el servidor | SEO | Datos dinámicos |
|---|---|---|---|
| CSR | No | Deficiente | Total |
| SSR | Sí (en cada petición) | Excelente | Alta |
| SSG | Sí (en la compilación) | Excelente | No (requiere recompilar) |
| ISR | Sí (por temporizador) | Excelente | Mediante revalidación |
| Enfoque | Ideal para |
|---|---|
| CSR | SPA autenticadas, paneles de control |
| SSR | Páginas personalizadas, catálogos |
| SSG | Blog, páginas de destino, documentación |
| ISR | Catálogos de precios, noticias |
SSR (Server-Side Rendering): el HTML se genera en el servidor en cada petición. Ofrece la máxima actualidad de los datos, pero aumenta la carga del servidor. En Next.js, generateMetadata y los componentes async del App Router aportan SSR por defecto.
SSG (Static Site Generation): el HTML se genera una sola vez, al compilar, y se sirve como archivo estático. Tiene el TTFB más bajo y es ideal para el SEO. Inconveniente: si cambian los datos, hay que recompilar.
ISR (Incremental Static Regeneration): un enfoque híbrido. Las páginas se compilan de forma estática, pero tras un periodo de revalidación (revalidate: 3600), el servidor las regenera en segundo plano con la siguiente petición. En Next.js es el enfoque estándar para la mayoría de las páginas.
// Next.js App Router — SSG + ISR
export const revalidate = 3600; // revalidate every hour
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Product Catalogue',
description: 'Full range with up-to-date prices',
};
}
// All critical content is server-rendered:
export default async function CatalogPage() {
const products = await fetchProducts(); // server fetch
return (
<main>
<h1>Product Catalogue</h1>
{products.map(p => <ProductCard key={p.id} {...p} />)}
</main>
);
}Enlaces dinámicos y navegación
Googlebot solo entiende los enlaces en formato <a href="/url">. Los enlaces implementados con onClick, button, data-href o window.location son invisibles para el rastreador. Por eso, las páginas a las que solo se llega por esa navegación pueden no indexarse.
- Usa
<a href>en todos los enlaces de navegación, nobutton onClickconrouter.push() - Paginación mediante parámetros de URL (
?page=2): Googlebot rastrea estas URL si aparecen en el HTML - Router de SPA: el
<Link>de Next.js y el<Link>de React Router generan etiquetas<a>correctas en el servidor - La navegación por hash (
#ancla) no crea URL separadas en el índice: no la uses para contenido que deba posicionarse por sí mismo - Menús dinámicos: si los elementos del menú se cargan desde una API tras la carga de la página, Google puede no descubrirlos hasta el renderizado
Carga diferida y scroll infinito
Googlebot carga las imágenes con loading="lazy": el rastreador moderno admite la carga diferida. Pero hay una condición: la imagen debe estar en el DOM con un atributo src correcto durante el SSR. Si el src lo asigna JavaScript en el navegador, el rastreador ve una etiqueta vacía.
El scroll infinito necesita una ruta rastreable hasta cada elemento relevante. Ofrece URL secuenciales y enlaces normales que funcionen sin que el usuario pulse ningún botón. Después, usa la Inspección de URL o un rastreo para comprobar que el HTML renderizado expone el contenido previsto.
| Patrón | ¿Lo ve Googlebot? | Recomendación |
|---|---|---|
| <img src="/img.webp" loading="lazy"> | Sí | Úsalo |
| <img data-src="/img.webp"> (el JS asigna src) | No | Evítalo |
| Scroll infinito sin URL | Solo la primera pantalla | Añade paginación |
| Contenido tras el botón 'Mostrar más' | No | Muestra una parte en el HTML |
| Pestañas con contenido oculto | Sí, con retraso | Duplícalo en el HTML |
Diagnóstico: cómo comprobar el renderizado de JavaScript
La pregunta clave del diagnóstico es: «¿qué ve Googlebot en mi página?». Hay varias herramientas para responderla.
Herramientas de diagnóstico
- Google Search Console → Inspección de URL: muestra una captura de la página tras el renderizado de Googlebot, la lista de recursos y los enlaces descubiertos. Es el método de verificación más directo.
- 'Ver como Googlebot' en GSC: ejecuta el rastreador real y devuelve el HTML tras el renderizado. Compáralo con el HTML fuente.
curlowget: obtienen el HTML sin procesar, sin JS. Si las metaetiquetas clave y el contenido aparecen en la salida decurl, entrarán en la primera oleada de indexación.- Desactivar JS en las DevTools de Chrome (F12 → Ctrl+Shift+P → 'Disable JavaScript'): una forma rápida de ver la página como la vería el rastreador, sin renderizado.
- Screaming Frog con renderizado: con la opción 'Spider > Configuration > Rendering > Googlebot' puedes rastrear el sitio con renderizado de JS y ver los enlaces encontrados tras el renderizado.
# Check what Google sees without JS rendering:
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page
# Find all title / meta description / h1 in HTML response:
curl -s https://example.com/page | grep -E '<title>|<meta name="description"|<h1'Lista de comprobación de JavaScript SEO
Comprobaciones clave
- El title y la meta descripción están en la respuesta HTML del servidor (SSR/SSG), no los genera el JS
- El H1, los H2 y el texto principal son accesibles sin JavaScript
- Los enlaces de navegación usan
<a href>, noonClicknibutton - Los datos estructurados (JSON-LD) están en el
<head>de la respuesta HTML - Las imágenes tienen un
srcestático en el HTML;loading="lazy"solo en las que no son LCP - El robots.txt no bloquea los archivos JS/CSS necesarios para el renderizado
- Ningún error de JS rompe el renderizado (compruébalo en la consola de Chrome DevTools de la página)
- Core Web Vitals: LCP, INP y CLS cumplen los valores umbral de Google
- La paginación se hace mediante parámetros de URL, no solo mediante el estado de JS
- Las etiquetas canonical (
<link rel="[canonical](/glossary/canonical)">) están en el HTML, no se asignan con JS
Preguntas frecuentes
¿Puede Google indexar JavaScript?
¿Un sitio en React o Next.js se indexa bien automáticamente?
¿Hace falta prerenderizado o renderizado dinámico?
¿Puedo usar React.lazy y code splitting?
¿Cómo compruebo que mi sitio se indexa correctamente?
Check it with SpiderHead
Free and local: run the audit on your own machine.



