Tempo de leitura: 13 minutos
SEO no Next.js é garantir que cada página chegue pronta no HTML, com texto, links e metadados, para o Google e para os robôs de IA. No Next.js, isso depende sobretudo da escolha entre SSR, SSG e ISR e de metadados, sitemap e robots.txt gerados no servidor. Porém, o streaming muda o que um robô sem JavaScript recebe.
Em resumo
- No App Router, as páginas são Server Components, e até os Client Components saem em HTML no primeiro acesso. Por isso, a diretiva
use clientsozinha não esconde texto. - Para um robô sem JavaScript, some o que depende do navegador, por exemplo dados buscados depois da hidratação e componentes com
ssr: false. - No Next.js 16.4.1, GPTBot, OAI-SearchBot, ClaudeBot e PerplexityBot ficam fora da lista de robôs do framework. Assim, nas páginas dinâmicas, eles recebem streaming e metadados em streaming, como um navegador.
- Para o SEO no Next.js, prefira página estática ou ISR nas páginas públicas, com os dados em cache. Depois, teste com curl, no user agent de cada robô.
O Next.js já é bom para SEO por padrão?
Em boa parte, sim, porque o Next.js entrega HTML pronto, que é a base do SEO. No App Router, páginas e layouts são Server Components, e a Metadata API gera as tags do <head>. Os riscos estão no streaming, nos metadados em streaming e nos dados buscados no navegador.
Este guia segue a documentação do Next.js 16.4.1. De acordo com a documentação de cache, os apps novos criados com o create-next-app já vêm com Cache Components. Nesse modelo, o build gera um shell estático da página, enquanto o que depende de dados sem cache chega depois, em streaming.
O Googlebot executa JavaScript e, portanto, lê a página montada. No entanto, o estudo da Vercel com a consultoria MERJ, de dezembro de 2024, mostrou outro quadro. Nele, os robôs testados da OpenAI, da Anthropic e do Perplexity não executavam JavaScript. A Vercel, empresa por trás do Next.js, vende hospedagem para ele.
Em setembro de 2026, porém, o dono de um site em Next.js relatou na DEV Community que o OAI-SearchBot e o GPTBot passaram a executar JavaScript. Ele não achou anúncio da OpenAI. Por isso, até haver confirmação, trate o HTML da primeira resposta como o que o robô de IA lê. O comparativo de SSR e CSR para SEO e IA mostra o que cada robô enxerga em cada modo de renderização.

Quando usar SSR, SSG, ISR ou renderização no cliente no Next.js?
No Next.js, use página estática (SSG) para conteúdo igual para todos, ISR quando ele muda de tempos em tempos e SSR quando depende de quem pede. Deixe a renderização no cliente para o que não precisa de robôs, como áreas logadas. A escolha, aliás, vale por página e até por componente.
- Home e páginas institucionais: SSG, já que o conteúdo é o mesmo para todos.
- Blog e documentação: SSG ou ISR, por exemplo com
generateStaticParamse um prazo de revalidação. - Produto e categoria: ISR, porque preço e estoque mudam, mas não a cada visita.
- Página personalizada: SSR, uma vez que ela lê
cookies(),headers()ousearchParams. - Área logada: renderização no cliente, pois os robôs não precisam dela.
Na documentação do Next.js, o SSR também aparece como renderização dinâmica, e o SSG, como pré-renderização. Ou seja, um componente fica dinâmico quando usa APIs do pedido, como cookies(). Esse termo, contudo, não é a renderização dinâmica do Google, que entrega HTML pronto só a robôs, no modelo do Prerender.io.
No Next.js, o ISR gera a página no build e a refaz em segundo plano. Conforme o guia de ISR do Next.js, a primeira visita após o prazo ainda recebe a versão antiga, enquanto a nova é gerada. Com Cache Components e Partial Prefetching, ligados nos apps novos, as URLs fora do generateStaticParams recebem primeiro o App Shell. O App Shell é a parte da página que não depende da URL. Por isso, liste no build as URLs que importam para a busca.
Como gerar title, description, canonical e Open Graph com a Metadata API?
Exporte um objeto metadata para dados fixos ou a função generateMetadata para dados que vêm do CMS ou da URL. Para o SEO, o Next.js transforma os dois em tags de metadados no HTML. Defina também metadataBase no layout raiz, para usar caminhos relativos no canonical e nas imagens de Open Graph.
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://www.exemplo.com.br'),
title: { default: 'Loja Exemplo', template: '%s | Loja Exemplo' },
}
// app/produtos/[slug]/page.tsx
import type { Metadata } from 'next'
import { getProduto } from '@/lib/dados'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const produto = await getProduto(slug)
return {
title: produto.nome,
description: produto.resumo,
alternates: { canonical: `/produtos/${slug}` },
openGraph: { images: [produto.imagem] },
}
}
Em página pré-renderizada, os metadados já saem no HTML do build. Em página dinâmica, porém, o Next.js envia a página antes de o generateMetadata terminar e acrescenta as tags ao <body> depois. A mudança veio na versão 15.2. Segundo a documentação do generateMetadata, o Googlebot interpreta esses metadados, pois executa JavaScript. Por outro lado, robôs que só leem HTML, como o facebookexternalhit, recebem as tags no <head>.
Com Cache Components, o generateMetadata segue as regras dos outros componentes. Assim, se ele lê params, cookies(), headers() ou dados sem cache, fica para a hora do pedido. Para manter os metadados no HTML pré-renderizado, então, ponha a busca de dados em cache com use cache e liste as URLs no generateStaticParams.
Se os metadados precisam mesmo ser dinâmicos, acrescente os robôs de IA à opção htmlLimitedBots. Já que ela substitui a lista padrão, copie a lista da sua versão e some os robôs. No entanto, a mesma documentação avisa que a resposta pode ficar mais lenta.
// next.config.ts
import type { NextConfig } from 'next'
// Lista padrão do Next.js 16.4.1 (html-bots.ts) mais os robôs de IA
const padrao = String.raw`[\w-]+-Google|Google-[\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight`
const robosDeIA = 'GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User'
const nextConfig: NextConfig = {
htmlLimitedBots: new RegExp(`${padrao}|${robosDeIA}`, 'i'),
}
export default nextConfig
Com Cache Components, há um efeito a mais. De acordo com a documentação de cache, os robôs da lista pulam o shell estático e recebem a página renderizada no pedido. Por isso, se um dado só existir no build, a página pode falhar para eles, com erro 500.
Componentes com use client escondem conteúdo dos robôs?
Não por si só, porque o Next.js também renderiza os Client Components no servidor no primeiro acesso, e o HTML deles vai na resposta. Para um robô sem JavaScript, no entanto, some o que depende do navegador. É o caso, por exemplo, de dados buscados após a hidratação, de componentes com ssr: false e de texto que só abre com um clique.
Para o SEO no Next.js, o guia sobre a fronteira entre servidor e cliente resume a regra:
“Um robô que lê só HTML vê a primeira resposta e não executa nada do seu JavaScript. Server Components e Client Components contribuem com HTML para essa resposta.” (tradução nossa)
Documentação do Next.js (2026)
| Padrão no código | Vai na primeira resposta? | O que fazer |
|---|---|---|
| Server Component | Sim | Nada, porque é o padrão |
| Client Component | Sim, pois roda também no servidor | Nada |
Texto buscado com useEffect, useSWR ou useQuery | Não, porque chega após a hidratação | Buscar no servidor e passar como prop |
dynamic() com ssr: false | Não | Usar só em widget sem texto importante |
<Suspense> ou loading.js com dados sem cache | Sim, mas no fim, num <div hidden> | Pôr os dados em cache |
| Aba que busca o texto no clique | Não | Renderizar o texto e só alternar a exibição |
O que os robôs de IA recebem do Next.js em streaming?
Recebem a mesma resposta de um navegador, já que o Next.js 16.4.1 não os reconhece como robôs. Assim, o conteúdo em streaming chega no fim do HTML, num bloco oculto, e os metadados dinâmicos chegam no <body>. Já os robôs da lista interna do framework recebem os metadados no <head>, embora o resto da página ainda possa vir em streaming.
Para decidir como responder, o Next.js olha o user agent. No código do Next.js 16.4.1, a lista interna inclui o Bingbot, o Twitterbot e outros robôs do Google, como o AdsBot-Google. O Googlebot fica fora dela, porque executa JavaScript e lê os metadados em streaming. Contudo, GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User, PerplexityBot e Perplexity-User também não estão na lista.
Segundo o guia de streaming, cada bloco de <Suspense> pronto chega depois, na mesma resposta. Ele vem num <div hidden>, com um script que o põe no lugar. Um loading.js, por exemplo, põe a página inteira dentro de um desses blocos.
O teste na demo de streaming do Next.js
Para ver o efeito no SEO do Next.js, testamos a demo de streaming citada nesse guia, que usa Cache Components. Com os user agents do GPTBot, do OAI-SearchBot, do ClaudeBot e do PerplexityBot, a página veio em três partes, assim como para o Chrome. Como resultado, os dois blocos de dados ficaram no fim do HTML, ocultos. Com o do Bingbot, porém, ela veio de uma vez, com o texto no lugar. A demo roda uma versão de testes da 16.2. Desde a 16.3, os robôs da lista recebem os metadados no <head>, mas o resto pode vir em streaming.
O texto continua na resposta. Aliás, o estudo da Vercel diz que conteúdo incluído no HTML inicial, como Server Components que chegam depois, ainda pode ser indexado. Mas ele chega fora do lugar e marcado como oculto. Além disso, as páginas da OpenAI, da Anthropic e do Perplexity sobre seus robôs não tratam disso. Portanto, deixe o texto principal no shell estático.
Como gerar sitemap, robots.txt e dados estruturados no Next.js?
No Next.js, o sitemap.xml e o robots.txt saem de arquivos especiais na pasta app. O JSON-LD, por sua vez, entra como tag <script> no page ou no layout. De acordo com a documentação do Next.js, o robots.txt e o sitemap são Route Handlers especiais, guardados em cache, salvo se usarem APIs do pedido.
// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getPosts } from '@/lib/dados'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts()
return posts.map((post) => ({
url: `https://www.exemplo.com.br/blog/${post.slug}`,
lastModified: post.atualizadoEm,
}))
}
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: '*', allow: '/', disallow: '/admin/' },
{ userAgent: 'GPTBot', disallow: '/' },
],
sitemap: 'https://www.exemplo.com.br/sitemap.xml',
}
}
Acima de 50 mil URLs, divida o sitemap com generateSitemaps, porque esse é o limite do Google por arquivo, lembrado na documentação. Depois, envie o sitemap ao Search Console e ao Bing Webmaster Tools.
No exemplo, o robots.txt gerado pelo Next.js libera todos os robôs, menos na pasta /admin/, e só o GPTBot fica de fora. Segundo a OpenAI, cada robô dela tem regra independente. Assim, bloquear o GPTBot sinaliza que o conteúdo não deve ir para o treino, enquanto o OAI-SearchBot segue liberado para a busca do ChatGPT. Qual robô liberar está no guia de como aparecer no ChatGPT. As regras para os outros robôs de IA estão no guia de robots.txt para IA.
Para dados estruturados, o guia de JSON-LD do Next.js recomenda uma tag <script> no page ou no layout. Porém, o componente next/script não serve aqui, porque JSON-LD não é código executável. Para evitar injeção de código, troque o caractere < por \u003c.
// app/produtos/[slug]/page.tsx (trecho)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: produto.nome,
description: produto.resumo,
}
return (
<section>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c') }}
/>
{/* ... */}
</section>
)
Por fim, deixe esse bloco fora de <Suspense>, para que ele saia no HTML do shell, e não no trecho em streaming.
Como testar o que o Googlebot e o GPTBot recebem?
Baixe a página com o user agent de cada robô e confira três pontos: o title no <head>, os blocos em streaming e o texto no lugar. Em seguida, compare o GPTBot com o Bingbot, que está na lista do Next.js e recebe os metadados no <head>. Uma diferença entre os dois indica metadados em streaming ou resposta diferente por robô.
Antes, rode next build. Conforme o guia de build, ○ marca página estática, ● páginas geradas pelo generateStaticParams e ƒ renderização a cada pedido. Com Cache Components, ◐ indica um shell estático com o resto em streaming, ou seja, o caso que pede o teste abaixo.
URL="https://www.exemplo.com.br/produtos/tenis-azul"
FRASE="uma frase fixa de um parágrafo do texto principal"
testar() {
curl -s -A "$2" "$URL" | tr -d '\n' > "$1.html"
titulo=$(grep -o '<head.*</head>' "$1.html" | grep -c '<title')
blocos=$(grep -o '<div hidden id="S:' "$1.html" | wc -l)
frase=$(perl -pe 's/<script.*?<\/script>//g; s/<div hidden id="S:.*//' "$1.html" | grep -c "$FRASE")
echo "$1 | title no head: $titulo | blocos em streaming: $blocos | frase no lugar: $frase"
}
testar gptbot 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot'
testar bingbot 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/W.X.Y.Z Safari/537.36'
Rode no terminal do Linux, do macOS ou no Git Bash do Windows. Use uma frase fixa, porque nomes ou números vindos do banco podem sair separados no HTML. Os user agents vêm das páginas oficiais da OpenAI e do Bing, e W.X.Y.Z marca a versão do navegador, como na fonte. Leia o resultado assim:
- Title no head só no Bingbot: ou seja, os metadados estão em streaming. Torne-os pré-renderizáveis ou use
htmlLimitedBots. - Blocos em streaming e frase fora do lugar no GPTBot: isto é, o texto principal está num
<Suspense>com dados sem cache. - Frase ausente nos dois: nesse caso, o texto é buscado no navegador. Portanto, leve a busca para um Server Component.
O que o teste com curl não mostra
O teste sai do seu IP, então não reproduz bloqueios de firewall ou CDN feitos por IP. Para o Googlebot, que executa JavaScript, o curl mostra só o que ele baixa. A página renderizada aparece na Inspeção de URL do Search Console. Confira também se o robots.txt gerado pelo Next.js libera os robôs que você quer. Nos logs, pedidos ?_rsc= vindos de robôs de IA indicam JavaScript executado, como no relato de setembro. O passo a passo dessas checagens está no guia de JavaScript SEO para robôs de IA.
Afinal, o SEO no Next.js é uma parte do SEO que sustenta a visibilidade da marca no Google e na IA.
Perguntas frequentes
Para robôs que não executam JavaScript, sim, porque o Next.js entrega o HTML do servidor por padrão, seja por SSR, seja por pré-renderização. Um app React só com renderização no cliente, por outro lado, começa com uma casca vazia e monta a página no navegador. O Googlebot executa JavaScript nos dois casos.
Não. Conforme a documentação, o Next.js roda num servidor Node.js comum, com next start, e atende todos os recursos, inclusive ISR e Cache Components. A exportação estática, com output: 'export', gera um HTML por rota, mas não tem ISR nem recursos que dependem do pedido, como cookies.
Serve, porque o Pages Router também pré-renderiza cada página por padrão. No Pages Router do Next.js, o getServerSideProps faz o SSR, e o getStaticProps com revalidate faz o ISR. Os metadados entram pelo componente Head, de next/head, enquanto a Metadata API e os Server Components são do App Router.
Fontes principais
- Next.js, The Server and Client Boundary, com o quadro sobre Server Components e SEO (2026).
- Next.js, generateMetadata e streaming de metadados (2026).
- Next.js, Streaming, com a seção sobre robôs e rastreadores (2026).
- Next.js, Caching e Cache Components (2026).
- Next.js, como ler o resultado do next build (2026).
- GitHub, vercel/next.js, lista padrão de robôs HTML-limited na versão 16.4.1 (2026).
- Vercel, The rise of the AI crawler, estudo com a MERJ (2024).
- OpenAI, Overview of OpenAI Crawlers (2026).


