Saltar al contenido

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.tsx como 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:

  1. 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>.
  2. 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 config

Si 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.xml

React 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:

  1. 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;
  1. 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.
  2. 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

  1. 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"
  1. «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.
  2. 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

  1. 1Next.js 16.4, Next.js Blog (Vercel), 06-10-2026.
  2. 2Understand JavaScript SEO basics, Google Search Central, actualizada el 04-03-2026.
  3. 3Overview of OpenAI Crawlers, OpenAI, consultada el 07-10-2026.
  4. 4Does Anthropic crawl data from the web, and how can site owners block the crawler?, Anthropic, actualizada el 07-04-2026.
  5. 5Perplexity Crawlers, Perplexity, consultada el 07-10-2026.
  6. 6The rise of the AI crawler, Giacomo Zecchini, Alice Alexandra Moore, Malte Ubl y Ryan Siddle, Vercel, 17-12-2024.
  7. 7Server and Client Components, documentación de Next.js, actualizada el 05-10-2026.
  8. 8How to lazy load Client Components and libraries, documentación de Next.js, actualizada el 10-03-2026.
  9. 9generateMetadata, documentación de Next.js, actualizada el 19-08-2026.
  10. 10How to implement JSON-LD in your Next.js application, documentación de Next.js, actualizada el 02-03-2026.
  11. 11Introduction to structured data markup in Google Search, Google Search Central, actualizada el 10-12-2025.
  12. 12htmlLimitedBots, documentación de Next.js, actualizada el 03-10-2025; lista por defecto en html-bots.ts, consultada el 07-10-2026.
  13. 13robots.txt, documentación de Next.js, actualizada el 01-05-2026.
  14. 14sitemap.xml, documentación de Next.js, actualizada el 18-08-2026.
  15. 15Google's common crawlers, Google Crawling Infrastructure, actualizada el 14-07-2026.
  16. 16Build and submit a sitemap, Google Search Central, actualizada el 08-07-2026.
  17. 17Creating a React App, documentación de React, consultada el 07-10-2026.
  18. 18Rendering Strategies, documentación de React Router, consultada el 07-10-2026.
  19. 19Why Astro?, documentación de Astro, consultada el 07-10-2026.
  20. 20Dynamic rendering as a workaround, Google Search Central, actualizada el 10-12-2025.
  21. 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/

Quién lo escribe

SmoothSeen es una herramienta de auditoría web que mide el posicionamiento en buscadores y en asistentes de IA y entrega informes con la marca de la agencia.

Este blog es de SmoothSeen: cuando un artículo habla del producto, lo hace sabiendo que es el nuestro. Las cifras de terceros enlazan a su fuente original.

Historial de cambios

  • Primera versión.

Sigue leyendo

SEO en Next.js y React: que la IA lea tu web