SEO técnico para aplicaciones React y SPA: guía práctica

Una aplicación React de página única (SPA) puede salir vacía para Google porque el HTML que entrega el servidor solo trae un div y un script: el contenido aparece cuando corre JavaScript. La solución de fondo es renderizar en el servidor y, alrededor de eso, cuidar títulos, canonicals, sitemap, redirecciones e indexación. Lo que sigue sale de ajustar nuestro propio sitio, hecho con Laravel, Inertia y React.
Por qué una SPA puede salir vacía para Google
Google procesa una página en dos fases: primero rastrea el HTML y después, cuando tiene recursos, la renderiza con un navegador. Si el HTML inicial no trae el texto, los enlaces ni el título, la página depende de esa segunda fase, que puede tardar o fallar si la API responde lento o el JavaScript lanza un error. Las vistas previas de WhatsApp o LinkedIn ni siquiera ejecutan JavaScript.
Compruébalo con el código fuente (Ctrl+U, no el inspector) o con curl:
curl -s https://tusitio.com/servicios | grep -i "<h1"
Si no aparece tu título, Google tampoco lo ve en la primera pasada. En Search Console, la inspección de URLs muestra el HTML que Google renderizó.
Renderiza en el servidor
Con renderizado en el servidor (SSR), cada URL llega con el HTML completo y luego React lo "hidrata" para hacerlo interactivo. Las opciones según tu pila:
- Next.js si empiezas en React, o Nuxt si trabajas con Vue.
- Inertia SSR si tu backend es Laravel: compilas un segundo paquete con vite build --ssr y dejas php artisan inertia:start-ssr corriendo como servicio de systemd o Supervisor.
- Prerenderizado al compilar, si el sitio tiene pocas páginas que cambian poco.
Lo que suele romper el SSR: tocar window o localStorage durante el render y compartir estado global entre peticiones. En nuestro caso, el idioma y el modo visual pasaron a cookies para que el servidor renderice lo mismo que verá el usuario, y cada petición usa su propia instancia de traducciones. Los paneles detrás de un login no necesitan SSR.
Un título y un canonical por URL
Cada URL debe producir un solo title, una meta description y un canonical absoluto hacia sí misma. El error típico en una SPA es que la plantilla base trae un title fijo y la página agrega otro, o que el canonical apunta siempre al inicio.
<title>Software de inventarios para pymes | Tu marca</title>
<meta name="description" content="Qué hace, para quién es y cuánto cuesta.">
<link rel="canonical" href="https://tusitio.com/servicios/inventarios">
Con Inertia, marca las etiquetas de la plantilla Blade con el atributo inertia y defínelas en cada página con el componente Head (y head-key en las meta), para que se reemplacen en vez de duplicarse.
Redirecciones 301 y la barra final
Si cambiaste URLs, redirige cada una a su equivalente con 301, no todas al inicio. Revisa también la barra final: detrás de nginx, Laravel responde 200 tanto en /servicios como en /servicios/, y eso es contenido duplicado. Nosotros lo resolvimos con un middleware que redirige con 301 toda URL terminada en barra, salvo la raíz y la API.
Sitemap dinámico
Genera el sitemap.xml desde la base de datos, para que cada página nueva entre sola, con URLs canónicas que respondan 200 (nada redirigido ni con noindex) y un lastmod que cambie solo cuando cambie el contenido. En Laravel lo resuelve spatie/laravel-sitemap.
Saca del índice paneles y APIs, y marca bien el resto
Paneles administrativos, APIs, páginas de cuenta y entornos de prueba no deben indexarse. Una API devuelve JSON, sin head donde poner una meta robots, así que la vía es la cabecera X-Robots-Tag. En nginx, en el server de cada subdominio:
# app.tusitio.com, api.tusitio.com
add_header X-Robots-Tag "noindex, nofollow" always;
Dos trampas. En nginx, un add_header dentro de un location anula los que heredaba del server; por eso conviene un snippet que incluyas en cada bloque. Y no bloquees esas URLs en robots.txt: si Google no puede rastrearlas, nunca ve el noindex y puede dejarlas indexadas sin descripción.
Datos estructurados válidos
Marca en JSON-LD solo lo que la página muestra: Organization o LocalBusiness con datos reales, BreadcrumbList, Article en el blog y FAQPage si las preguntas están visibles. No agregues calificaciones ni reseñas que no vengan de clientes reales mostrados en la página: va contra las pautas de Google, que además no muestra estrellas cuando un negocio se califica a sí mismo. Valida con la Prueba de resultados enriquecidos antes de publicar.
Core Web Vitals: que el contenido no arranque invisible
Google mide tres métricas con datos de usuarios reales, y estos son sus umbrales de "bueno": LCP hasta 2,5 segundos (cuándo se ve el elemento principal), INP hasta 200 ms (qué tan rápido responde a un toque) y CLS hasta 0,1 (cuánto se mueve el diseño). En una SPA con animaciones, un problema frecuente es el contenido que arranca invisible:
// Evítalo en el título: llega invisible y espera al JavaScript
<motion.h1 initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
Con SSR el texto ya está en el HTML, pero oculto hasta que React hidrata y corre la animación. Anima elementos secundarios, nunca el título ni la imagen principal. Además:
- Declara width y height en las imágenes para que el diseño no salte.
- Carga las fuentes con font-display: optional o swap.
- Divide el JavaScript por ruta, para que el inicio no descargue el panel completo.
Mide con PageSpeed Insights y Search Console: para Google cuentan los datos de usuarios reales, no la puntuación de laboratorio.
Search Console e IndexNow
Verifica la propiedad de dominio en Search Console (con un registro DNS cubre todos los subdominios), envía el sitemap y revisa el informe de páginas. "Rastreada: actualmente sin indexar" suele señalar contenido corto o repetido más que un fallo técnico. Para páginas clave recién publicadas, pide la indexación desde la inspección de URLs.
IndexNow avisa al instante a Bing, Yandex y otros buscadores cuando publicas o cambias una URL; Google no lo usa. Publicas un archivo con tu clave en la raíz del sitio y, en cada despliegue, envías las URLs del sitemap:
curl -X POST https://api.indexnow.org/indexnow \
-H "Content-Type: application/json" \
-d '{"host":"tusitio.com","key":"TU_CLAVE","urlList":["https://tusitio.com/blog/nuevo"]}'
Si tu aplicación React no aparece en Google y no sabes si el problema es el renderizado, los canonicals o el contenido, una consultoría técnica de pocas horas lo diagnostica con tu equipo. Si necesitas una página rápida y bien armada para buscadores, mira nuestras landing pages; para un sistema completo, el desarrollo de software a la medida. Escríbenos para cotizar el diagnóstico.