SEO en Next.js y React: cómo hacer que la IA lea tu contenido sin JavaScript
Publicado el 07-10-20269 min de lecturaPor la redacción de SmoothSeen
Para que la IA lea una web hecha con React, el contenido principal tiene que llegar en el HTML que sirve el servidor. Google ejecuta JavaScript en una segunda fase, pero OpenAI, Anthropic y Perplexity no documentan que sus rastreadores lo hagan. En Next.js, los Server Components ya lo resuelven; una SPA con Vite necesita prerenderizar o un framework con SSR.
Lo esencial
- Google ejecuta JavaScript en una segunda fase, pero OpenAI, Anthropic y Perplexity no documentan que sus rastreadores lo hagan; un estudio de Vercel de diciembre de 2024 no vio a ninguno ejecutarlo.
- En Next.js con App Router, las páginas son Server Components y llegan con el texto en el HTML; el riesgo está en cargar el contenido en un useEffect o con ssr false.
- En una prueba con Next.js 16.4, unos metadatos lentos en generateMetadata llegaron al body, no al head, para OAI-SearchBot, ClaudeBot y PerplexityBot.
- Una SPA de React con Vite necesita prerenderizar o pasar a un framework con renderizado en servidor; Google llama al dynamic rendering un apaño, no una solución.
Para comprobarlo en tu web: Auditoría de visibilidad en IA
En esta página
Una SPA (aplicación de una sola página) envía un HTML casi vacío y monta el contenido en el navegador con JavaScript. Para una persona no hay diferencia. Para un rastreador que lee el HTML y no ejecuta el código, la página dice «Cargando…». Esta guía explica qué rastreador ve qué, cómo lo resuelve Next.js (versión 16.4, publicada el 06-10-20261) y qué hacer si tu web es React puro con Vite.
¿Quién ejecuta JavaScript y quién no?
Google procesa las páginas en tres fases: rastreo, renderizado e indexación. Las páginas esperan en una cola de renderizado que puede durar segundos o más, y después un Chromium sin interfaz ejecuta el JavaScript2. Aun así, Google recomienda el renderizado en servidor o el prerenderizado porque hace la web más rápida y porque no todos los bots pueden ejecutar JavaScript2.
Los rastreadores de IA son esos bots. Ni OpenAI, ni Anthropic, ni Perplexity dicen en la documentación de sus rastreadores que ejecuten JavaScript345. El dato más citado es un estudio de Vercel del 17-12-2024, que analizó un mes de peticiones de rastreadores en su red (569 millones de GPTBot y 370 millones de Claude, entre otros). Sus conclusiones, para ese periodo6:
- Rastreador
- Googlebot (y Gemini, que usa su infraestructura)
- ¿Ejecutaba JavaScript en el estudio de Vercel?
- Sí
- Rastreador
- Applebot
- ¿Ejecutaba JavaScript en el estudio de Vercel?
- Sí
- Rastreador
- OAI-SearchBot, ChatGPT-User, GPTBot
- ¿Ejecutaba JavaScript en el estudio de Vercel?
- No: descargaban archivos JS sin ejecutarlos
- Rastreador
- ClaudeBot
- ¿Ejecutaba JavaScript en el estudio de Vercel?
- No
- Rastreador
- PerplexityBot
- ¿Ejecutaba JavaScript en el estudio de Vercel?
- No
Es un estudio de un proveedor, de 2024, y los rastreadores cambian. Pero ninguna documentación oficial lo contradice, así que la regla práctica es: si un texto no está en el HTML inicial, da por hecho que ChatGPT, Claude y Perplexity no lo ven.
Next.js App Router: qué llega en el HTML y qué no
En el App Router, las páginas y los layouts son Server Components por defecto: leen los datos en el servidor y Next.js genera el HTML con el contenido7. Los Client Components, los que llevan 'use client', también se prerenderizan a HTML en la primera carga7. El problema no es 'use client': es pedir el texto desde el navegador.
Probamos cada patrón en una aplicación con Next.js 16.4.0 en modo producción, pidiendo las páginas con el user-agent de cada rastreador:
- Patrón
page.tsxcomo Server Component que lee los datos- ¿El texto llega en el HTML?
- Sí
- Patrón
- Client Component que recibe el texto por props
- ¿El texto llega en el HTML?
- Sí, prerenderizado en el servidor
- Patrón
- Client Component que pide el texto en un
useEffect - ¿El texto llega en el HTML?
- No: el HTML dice «Cargando…»
- Patrón
- Componente cargado con
dynamic(..., { ssr: false }) - ¿El texto llega en el HTML?
- No: se pinta solo en el navegador8
Este es el antipatrón de la tercera fila. GPTBot recibió <article>Cargando…</article> y nada más:
'use client'
import { useEffect, useState } from 'react'
// Antipatrón: el texto principal llega después, desde el navegador
export default function Page() {
const [texto, setTexto] = useState('')
useEffect(() => {
fetch('/api/articulo')
.then((r) => r.json())
.then((d) => setTexto(d.cuerpo))
}, [])
return <article>{texto || 'Cargando…'}</article>
}La solución es leer los datos en el Server Component de la página y pasar a un Client Component solo lo interactivo, como un botón de «me gusta». ssr: false déjalo para lo que no importa leer: un mapa, un chat, un editor.
Metadatos y canonical con generateMetadata
Los metadatos se declaran con el objeto metadata o con la función generateMetadata, que solo funcionan en Server Components9. Fija la URL base una vez en el layout raíz con metadataBase: new URL('https://www.example.com'), y cada página puede usar rutas relativas, también en el canonical9. Esta es la página de un artículo, con título, descripción, canonical y datos estructurados:
import type { Metadata } from 'next'
import { getPost } from '@/lib/posts'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.summary,
alternates: { canonical: `/blog/${slug}` },
}
}
export default async function Page({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
}
return (
<article>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
<h1>{post.title}</h1>
<p>{post.summary}</p>
<div>{post.body}</div>
</article>
)
}El bloque JSON-LD sigue la guía oficial de Next.js: una etiqueta <script> nativa en la página y el carácter < sustituido por < para evitar inyecciones de código10. Google acepta el JSON-LD también en el <body>11.
El detalle del streaming de metadatos
Desde la versión 15.2, si generateMetadata tarda, Next.js envía primero la página y añade los metadatos después, al final del <body>. Solo espera para meterlos en el <head> con una lista de bots que no ejecutan JavaScript, la opción htmlLimitedBots9. En la lista por defecto, consultada hoy en el código de Next.js, están Bingbot, DuckDuckBot, Applebot o Twitterbot, pero ningún rastreador de IA12.
Lo comprobamos con un generateMetadata que tardaba un segundo y medio: OAI-SearchBot, GPTBot, ClaudeBot, Claude-SearchBot y PerplexityBot recibieron el <title> y la descripción dentro del <body>; Bingbot, en el <head>. Ningún rastreador de IA documenta si lee metadatos fuera del <head>. Hay dos salidas:
- Que los metadatos no tarden: si los datos están en caché o la página se prerenderiza, los metadatos van en el HTML inicial9. En la misma prueba, con datos rápidos, todos los bots los recibieron en el
<head>. - Desactivar el streaming de metadatos con la configuración que documenta Next.js, a cambio de una respuesta inicial algo más lenta para todos12:
import type { NextConfig } from 'next'
const config: NextConfig = {
htmlLimitedBots: /.*/,
}
export default configSi prefieres una lista propia, ten en cuenta que htmlLimitedBots sustituye la lista por defecto, no la amplía12.
robots.ts y sitemap.ts
Next.js genera robots.txt y sitemap.xml desde dos archivos en la raíz de app1314. Este app/robots.ts deja paso a todos, incluidos Googlebot y los buscadores de IA, y veta solo el entrenamiento de modelos:
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
// Todos los demás, incluidos Googlebot y los buscadores de IA
{ userAgent: '*', allow: '/', disallow: '/admin/' },
// Entrenamiento de modelos: vetarlo no te saca de las respuestas
{
userAgent: ['GPTBot', 'ClaudeBot', 'Google-Extended', 'Applebot-Extended', 'CCBot'],
disallow: '/',
},
],
sitemap: 'https://www.example.com/sitemap.xml',
}
}OpenAI separa GPTBot, de entrenamiento, de OAI-SearchBot, que decide si apareces en la búsqueda de ChatGPT3, y Anthropic hace lo mismo con ClaudeBot frente a Claude-SearchBot y Claude-User4. Google-Extended no afecta a la Búsqueda de Google15.
Y app/sitemap.ts, a partir de los artículos:
import type { MetadataRoute } from 'next'
import { getPosts } from '@/lib/posts'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts()
return [
{ url: 'https://www.example.com', lastModified: '2026-10-01' },
...posts.map((post) => ({
url: `https://www.example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
})),
]
}La documentación de Next.js incluye changeFrequency y priority en sus ejemplos, pero Google ignora los dos valores; sí usa lastmod si es exacto16. Los dos archivos se compilaron sin errores de tipos con Next.js 16.4.0 y generaron este robots.txt:
User-Agent: *
Allow: /
Disallow: /admin/
User-Agent: GPTBot
User-Agent: ClaudeBot
User-Agent: Google-Extended
User-Agent: Applebot-Extended
User-Agent: CCBot
Disallow: /
Sitemap: https://www.example.com/sitemap.xmlReact con Vite (SPA): tres salidas
Si tu web es una SPA de React montada con Vite, el HTML que recibe un rastreador es un <div id="root"> vacío. React recomienda empezar los proyectos nuevos con un framework, y cita Next.js y React Router17. Tus opciones:
- Prerenderizar en la compilación. Generas el HTML de cada ruta al compilar. Si usas React Router en modo framework, basta con listar las rutas en
react-router.config.ts18:
import type { Config } from "@react-router/dev/config";
export default {
async prerender() {
return ["/", "/about", "/contact"];
},
} satisfies Config;- Pasar a renderizado en servidor: Next.js, React Router con
ssr: true18 o Astro, que renderiza en el servidor y envía cero JavaScript por defecto, con componentes de React donde hagan falta19. - No uses dynamic rendering (servir HTML prerenderizado solo a los bots). Google dice que fue un apaño y no una solución a largo plazo, y recomienda en su lugar el renderizado en servidor, el estático o la hidratación20.
Cómo comprobarlo
- Pide la página como un rastreador de IA y busca una frase de tu contenido y el título:
curl -s -A "Mozilla/5.0 (compatible; OAI-SearchBot/1.3; +https://openai.com/searchbot)" https://www.example.com/blog/hola-mundo | grep -o "<title>.*</title>"
curl -s -A "Mozilla/5.0 (compatible; OAI-SearchBot/1.3; +https://openai.com/searchbot)" https://www.example.com/blog/hola-mundo | grep -c "una frase de tu artículo"- «Ver código fuente» frente a «Inspeccionar». El código fuente (Ctrl+U) es el HTML que llega del servidor; el inspector enseña la página después de ejecutar JavaScript. Si el texto solo está en el inspector, los rastreadores de IA no lo ven.
- Inspección de URL de Search Console. La prueba en directo enseña una captura de cómo ve la página la herramienta de Google y el HTML, las cabeceras y los mensajes de la consola de JavaScript21. Te dice lo que ve Google después de renderizar, no lo que ve un rastreador que no ejecuta JavaScript.
Qué hace SmoothSeen con esto
SmoothSeen mide, en Posicionamiento IA, cuánto texto llega en el HTML sin JavaScript frente a la página ya pintada en un navegador, y si la respuesta está al principio. También lee tu robots.txt separando búsqueda de entrenamiento y pide la página con el user-agent de cada rastreador de IA para detectar un 403. En Posicionamiento web revisa títulos, canonical, sitemap y datos estructurados.
Qué hacer esta semana
Elige tu página más importante y pásale el curl de arriba con una frase del segundo párrafo. Si no aparece, busca qué componente la carga en el navegador y muévelo al servidor. Si quieres medir la diferencia entre el HTML y la página pintada, analízala con SmoothSeen.
Preguntas frecuentes
¿Google indexa las webs hechas con React?
Sí. Googlebot ejecuta JavaScript con un Chromium sin interfaz en una fase de renderizado posterior al rastreo, y la página puede esperar en la cola desde unos segundos hasta más tiempo. Aun así, Google recomienda el renderizado en servidor o el prerenderizado, porque la web carga antes y porque otros bots no ejecutan JavaScript.
¿ChatGPT ejecuta JavaScript al leer una página?
OpenAI no lo dice en la documentación de sus rastreadores. El estudio de Vercel de diciembre de 2024 vio que OAI-SearchBot, ChatGPT-User y GPTBot descargaban archivos JavaScript sin ejecutarlos. Mientras no haya una documentación que diga otra cosa, trabaja como si solo leyeran el HTML que envía tu servidor.
¿Usar 'use client' en Next.js perjudica al SEO?
No por sí mismo. Los Client Components también se prerenderizan a HTML en el servidor en la primera carga, así que el texto que reciben por props llega a los rastreadores. Lo que perjudica es pedir el contenido desde el navegador, con un fetch en useEffect, o cargar el componente con ssr false.
¿Sirve el dynamic rendering para que los bots vean mi SPA?
Google lo describe como un apaño que no es una solución a largo plazo y recomienda en su lugar el renderizado en servidor, el renderizado estático o la hidratación. Además, obliga a mantener una lista de bots y dos versiones de cada página. Prerenderizar en la compilación o pasar a un framework con SSR resuelve lo mismo para todos los visitantes.
Fuentes
- 1Next.js 16.4, Next.js Blog (Vercel), 06-10-2026.
- 2Understand JavaScript SEO basics, Google Search Central, actualizada el 04-03-2026.
- 3Overview of OpenAI Crawlers, OpenAI, consultada el 07-10-2026.
- 4Does Anthropic crawl data from the web, and how can site owners block the crawler?, Anthropic, actualizada el 07-04-2026.
- 5Perplexity Crawlers, Perplexity, consultada el 07-10-2026.
- 6The rise of the AI crawler, Giacomo Zecchini, Alice Alexandra Moore, Malte Ubl y Ryan Siddle, Vercel, 17-12-2024.
- 7Server and Client Components, documentación de Next.js, actualizada el 05-10-2026.
- 8How to lazy load Client Components and libraries, documentación de Next.js, actualizada el 10-03-2026.
- 9generateMetadata, documentación de Next.js, actualizada el 19-08-2026.
- 10How to implement JSON-LD in your Next.js application, documentación de Next.js, actualizada el 02-03-2026.
- 11Introduction to structured data markup in Google Search, Google Search Central, actualizada el 10-12-2025.
- 12htmlLimitedBots, documentación de Next.js, actualizada el 03-10-2025; lista por defecto en html-bots.ts, consultada el 07-10-2026.
- 13robots.txt, documentación de Next.js, actualizada el 01-05-2026.
- 14sitemap.xml, documentación de Next.js, actualizada el 18-08-2026.
- 15Google's common crawlers, Google Crawling Infrastructure, actualizada el 14-07-2026.
- 16Build and submit a sitemap, Google Search Central, actualizada el 08-07-2026.
- 17Creating a React App, documentación de React, consultada el 07-10-2026.
- 18Rendering Strategies, documentación de React Router, consultada el 07-10-2026.
- 19Why Astro?, documentación de Astro, consultada el 07-10-2026.
- 20Dynamic rendering as a workaround, Google Search Central, actualizada el 10-12-2025.
- 21URL Inspection tool, Ayuda de Search Console, consultada el 07-10-2026.
Cómo citar este artículo
SmoothSeen. (2026, 7 de octubre). SEO en Next.js y React: cómo hacer que la IA lea tu contenido sin JavaScript. https://smoothseen.com/es/blog/nextjs-react-seo-ia/
Sigue leyendo
Posicionamiento en IA: guía de AEO y GEO para que ChatGPT, Gemini y Google te citen
Qué es el posicionamiento en IA (AEO y GEO), cómo eligen fuentes ChatGPT, Gemini y Google, qué bots dejar pasar y una checklist por bloques.
AEO, GEO y SEO: qué es cada uno y en qué se diferencian (con ejemplos)
Diferencia entre AEO, GEO y SEO: qué busca cada uno, dónde aparece el resultado, qué señales pesan y cómo se mide. Con tabla y un ejemplo.
Bloquear bots de IA en Cloudflare sin desaparecer de ChatGPT ni de Google
Qué ajuste de Cloudflare elegir para frenar el entrenamiento de IA sin bloquear a OAI-SearchBot, PerplexityBot ni Googlebot. Con reglas de WAF probadas.