Solicita hoy tu auditoría SEO gratuita Llama al 91 060 30 90
</>Guía Técnica · 18 min de lectura

SEO técnico para SPA: enrutamiento del lado del cliente e indexabilidad

La guía de JavaScript y renderizado de esta misma sección cubre cómo Google ejecuta JavaScript para ver el contenido final de una página. Esta guía se centra en un problema distinto y anterior a ese: en una SPA (Single Page Application), cambiar de "pantalla" no siempre implica cambiar de URL, y si no cambia la URL, no hay nada nuevo que un rastreador pueda descubrir, visitar o indexar por separado, por muy bien que se renderice el contenido una vez que se llega ahí.

Esta guía trata específicamente el enrutamiento: cómo se implementa correctamente con la History API para que cada vista relevante de la aplicación tenga su propia URL real, y los errores de enrutamiento (no de renderizado) que hacen invisibles secciones enteras de una SPA para SEO aunque el JavaScript se ejecute sin errores.

El problema de raíz: navegación sin cambio de URL

Una SPA carga un único documento HTML inicial y después sustituye el contenido de la página dinámicamente con JavaScript según el usuario navega, sin volver a pedirle al servidor un documento nuevo. Si esa navegación interna no actualiza también la URL visible en la barra de direcciones, desde la perspectiva de un rastreador nunca ha pasado nada: sigue viendo la misma URL, el mismo documento, y no tiene ningún motivo para volver a visitarla pensando que hay contenido nuevo que descubrir, porque no hay ninguna URL nueva que apunte a ese contenido.

Esto no es un problema exclusivo de SPAs mal hechas: es el comportamiento por defecto de enrutar solo con JavaScript en memoria (cambiando qué componente se renderiza) sin tocar la URL. Cualquier framework de SPA moderno resuelve esto con un router dedicado (React Router, Vue Router, Angular Router), pero el router hay que configurarlo y usarlo correctamente; no basta con instalarlo.

History API: pushState y replaceState como base técnica

Los routers de SPA se apoyan en la History API del navegador, concretamente en los métodos history.pushState() y history.replaceState(), que permiten cambiar la URL mostrada en la barra de direcciones (y añadir una entrada al historial de navegación) sin provocar una recarga completa de la página:

// Al navegar a la vista de un producto dentro de la SPA
history.pushState({ productId: 48291 }, '', '/productos/silla-nordica-roble');

Cuando esto se implementa correctamente, cada vista con valor propio (una ficha de producto, un artículo, una categoría) tiene una URL real, visitable directamente escribiéndola en el navegador, compartible, y por tanto descubrible y rastreable por Google de forma independiente. Cuando no se implementa (el router cambia solo un estado interno de React o Vue sin llamar a pushState), esa misma vista es efectivamente invisible para cualquiera que no sea un usuario haciendo clic manualmente dentro de la sesión.

Enlaces reales (<a href>) frente a manejadores de clic en JavaScript

Un error de enrutamiento muy común en SPAs no está en el router en sí, sino en cómo se construyen los enlaces que lo activan. Un enlace implementado como un elemento sin href real que solo responde a un evento onClick en JavaScript no es un enlace rastreable: Googlebot descubre nuevas URLs principalmente siguiendo atributos href en elementos <a>, no ejecutando manejadores de eventos arbitrarios para ver a dónde llevan.

<!-- No rastreable: sin href, depende de JS para navegar -->
<span onclick="navigateTo('/productos/silla-nordica')">Ver silla</span>

<!-- Rastreable: href real, el router intercepta el clic para SPA-navigate -->
<a href="/productos/silla-nordica" onclick="handleClick(event)">Ver silla</a>

La segunda versión es la que usan correctamente los routers de SPA maduros: el href apunta a la URL real (así un rastreador, o un usuario sin JavaScript, o alguien que abre el enlace en pestaña nueva, llegan a un sitio funcional), y el JavaScript intercepta el clic con event.preventDefault() para evitar la recarga completa y navegar internamente en su lugar, pero manteniendo sincronizada la URL con pushState.

Rutas anidadas y parámetros dinámicos: que el servidor sepa responder también

Una SPA con enrutamiento del lado del cliente necesita que el servidor también sepa qué responder cuando alguien (un rastreador, un usuario que recarga la página, alguien que llega desde un enlace externo) pide directamente una de esas URLs internas, como /productos/silla-nordica-roble, sin haber navegado primero por la aplicación. Si el servidor no está configurado para servir el documento HTML principal de la SPA para cualquier ruta que coincida con el patrón del router, esa petición directa devuelve un 404 real, aunque la misma URL funcione perfectamente si se llega a ella navegando desde dentro de la aplicación:

# Apache: servir index.html para cualquier ruta que no sea un archivo real
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]

Esta configuración (habitual en SPAs desplegadas como archivos estáticos) es la que hace que un rastreador, que siempre pide URLs directamente sin pasar por la navegación interna de la aplicación, obtenga una respuesta válida en vez de un 404, permitiendo que el JavaScript se cargue y el router interno de la SPA reconozca la ruta y renderice el contenido correcto para esa URL concreta.

Estados de carga y contenido que "aparece" después: el timing importa

Incluso con el enrutamiento resuelto, una SPA suele mostrar primero un estado de carga (un esqueleto, un spinner, un contenedor vacío) mientras hace una petición a una API para obtener los datos reales de esa vista. Un rastreador que ejecuta JavaScript puede llegar a ver el contenido final, pero solo si espera lo suficiente antes de capturar el HTML renderizado; si la petición a la API tarda más de lo que el rastreador está dispuesto a esperar, lo que se captura es el estado de carga vacío, no el contenido real.

Esto no tiene una solución puramente de enrutamiento: mitiga el problema mantener las peticiones de datos rápidas para las vistas que importan para SEO, y para casos críticos (páginas de producto o de contenido principal), valorar renderizado del lado del servidor o pre-renderizado estático en vez de depender por completo de que el rastreador espere pacientemente a una API lenta.

Sitemap y enlazado interno: no dependas solo del descubrimiento por navegación

Incluso con URLs técnicamente correctas y accesibles directamente, sigue siendo buena práctica declarar explícitamente en un sitemap XML todas las rutas de la SPA con valor para SEO, en vez de asumir que Google las va a descubrir solo siguiendo enlaces internos generados dinámicamente por JavaScript. El enlazado interno real (con href, no solo manejadores de clic) sigue siendo la señal más fuerte de qué páginas importan dentro del sitio, así que una arquitectura de navegación clara dentro de la SPA sigue siendo tan relevante para SEO como en un sitio tradicional.

Preguntas frecuentes

¿Es obligatorio usar renderizado del lado del servidor (SSR) en una SPA para que sea indexable?

No es obligatorio si el enrutamiento con History API está bien implementado y Google consigue renderizar el contenido a tiempo, pero SSR o pre-renderizado reduce la dependencia de que el rastreo con JavaScript funcione perfectamente en cada visita, y suele mejorar también la velocidad percibida por usuarios reales.

¿Por qué una URL de mi SPA funciona al navegar dentro de la app pero da 404 si la pego directamente en el navegador?

Porque el servidor no está configurado para servir el documento principal de la SPA ante cualquier ruta que coincida con el patrón del router; solo responde correctamente a la ruta raíz o a archivos que existen físicamente.

¿Los frameworks de SPA modernos resuelven esto automáticamente?

El router (React Router, Vue Router) resuelve la parte de la History API si se usa correctamente, pero la configuración del servidor para servir el documento HTML en cualquier ruta, y el uso de enlaces reales con href, siguen siendo responsabilidad de quien implementa el sitio.

¿Un hash en la URL (#/productos/silla) sirve igual que una ruta con History API?

No de forma fiable. Los rastreadores tratan históricamente el fragmento después de # como parte de la misma URL, no como una ruta distinta, así que ese patrón de enrutamiento (habitual en SPAs antiguas) es mucho menos indexable que rutas reales con pushState.

¿Cómo compruebo si Google está viendo el contenido real de mi SPA?

La herramienta de inspección de URLs de Search Console permite ver el HTML renderizado tal y como lo captura Google para una URL concreta, lo que revela si el contenido dinámico está presente o si solo se captura el estado de carga vacío.

¿Hablamos de SEO técnico para tu web?

Cuéntanos tu proyecto y te decimos cómo podemos ayudarte, sin compromiso.

Llama al 91 060 30 90