SEO para Next.js

Como tornar um site Next.js rastreável, indexável e ranqueável — os dois roteadores, modos de renderização (SSG/SSR/ISR/Server Components), a API de Metadados do App Router, sitemap.ts, robots.ts, next/image, next/link e os erros que silenciosamente quebram isso.

Publicado pela primeira vez: 26 de jun. de 2026 · Última atualização: 3 de ago. de 2026 · Avançado
Idiomas

O Next.js resolve os problemas mais difíceis de SEO em JavaScript por padrão — se você usá-lo corretamente. Os Server Components do App Router e SSG/ISR colocam o conteúdo no HTML, então não há atraso na fila de renderização; a API de Metadados nativa resolve títulos, canônicos e Open Graph no servidor; sitemap.ts e robots.ts são convenções de arquivo. O framework fornece a infraestrutura, mas não escreve nenhuma das suas tags. As falhas são previsíveis: metadataBase ausente, sem prioridade na imagem LCP, metadados exportados de um Client Component (silenciosamente não faz nada) e visualizações 404 retornando 200.

TL;DR — O Next.js resolve os problemas mais difíceis de SEO em JavaScript por padrão quando você o usa corretamente. O App Router usa Server Components por padrão e suporta SSG/SSR/ISR — todos os quais enviam HTML renderizado, então não há atraso na fila de renderização. Defina metadados com a Metadata API nativa (metadata / generateMetadata), e lembre-se de metadataBase ou seus canônicos e imagens OG ficarão relativos. Use app/sitemap.ts e app/robots.ts, pré-construa rotas dinâmicas com generateStaticParams, defina priority na imagem LCP e mantenha os links como âncoras reais de next/link. O Pages Router também é capaz de SEO via next/head. As falhas são previsíveis — e a maioria delas não é culpa do Next.js, é sua.

Onde o Next.js se encaixa

Next.js é um framework React, então tudo em SEO de JavaScript se aplica. O que faz valer a pena ter um guia próprio é que o Next.js oferece respostas de primeira linha para a maioria dos problemas de SEO em JS: renderização no servidor, geração estática, um sistema de metadados e convenções de sitemap/robots. A parte difícil não é se o Google consegue ler — o Google renderiza JavaScript há anos — é escolher o modo de renderização certo e não deixar o básico de SEO sem conexão. Este é um caso especializado de SEO de CMS headless: o CMS quase não importa, as decisões de renderização do frontend decidem tudo.

Uma estrutura para manter em mente: “este é um site Next.js” não diz como qualquer URL individual é entregue. O modo de renderização, o cache e os limites de Server/Client Components são definidos por rota (às vezes por segmento) — um projeto pode misturar uma página de marketing estática, uma página de produto com SSR e um dashboard com Client Components. Não extrapole o comportamento de uma rota para “todo o app”; teste a URL específica.

Dois roteadores, dois conjuntos de mecânicas

O Next.js tem dois roteadores, e eles lidam com SEO de maneiras diferentes:

  • Pages Router (o modelo mais antigo) — busca de dados via getStaticProps / getServerSideProps; metadados via <Head> de next/head (ou o pacote next-seo); sem Server Components.
  • App Router (v13+, a abordagem atual e recomendada) — React Server Components por padrão; a Metadata API nativa (exportação metadata / generateMetadata); convenções de arquivo para app/sitemap.ts e app/robots.ts; generateStaticParams para rotas dinâmicas. Evidence for this claim The App Router uses Server Components and supports generateStaticParams plus metadata file conventions. Scope: Current Next.js App Router behavior; route rendering can become dynamic based on APIs used. Confidence: high · Verified: Next.js: Server and Client Components Next.js: generateStaticParams

Ambos podem ranquear bem. O App Router oferece um sistema de metadados mais limpo e integrado (sem manipulação de next/head) e Server Components prontos para uso, por isso eu o escolheria em um novo projeto. Mas “App Router ou você não consegue fazer SEO” é um mito — muitos sites com Pages Router ranqueiam bem.

Modos de renderização e o que cada um significa para SEO

Como o Google lida com JavaScript é um pipeline de três fases — rastreamento, depois uma onda de renderização adiada, e então indexação. “Todas as páginas com código de status HTTP 200 são enviadas para a fila de renderização.” Todo o jogo no Next.js é escolher um modo que coloque seu conteúdo no HTML antes dessa onda de renderização, para que não haja nada para esperar. Evidence for this claim Google crawls, renders, and indexes JavaScript pages, and successful pages can enter the rendering queue. Scope: Google Search processing, not a promise that a URL will be indexed. Confidence: high · Verified: Google: JavaScript SEO basics

  • Static Site Generation (SSG) — páginas pré-renderizadas no momento do build. O HTML fica imediatamente disponível, sem risco de fila de renderização. Melhor para conteúdo que não muda a cada minuto. generateStaticParams() (App Router) / getStaticPaths() (Pages Router) decide quais rotas dinâmicas são pré-construídas.
  • Incremental Static Regeneration (ISR) — páginas estáticas que revalidam após um intervalo definido (export const revalidate = 3600). Os rastreadores recebem HTML estático com TTFB baixo e o conteúdo permanece atualizado. Um padrão forte — com uma armadilha: após o intervalo expirar, a próxima solicitação (possivelmente Googlebot) ainda recebe a página desatualizada; a versão atualizada é servida na solicitação seguinte. Para dados realmente voláteis (preços, estoque), SSR é mais seguro.
  • Server-Side Rendering (SSR) — HTML renderizado por solicitação. Os rastreadores recebem HTML totalmente renderizado imediatamente; o trade-off é a latência do servidor, então observe TTFB e LCP. export const dynamic = 'force-dynamic' ou o uso de APIs em tempo de solicitação (cookies, headers) opta por uma rota em SSR.
  • React Server Components (padrão do App Router) — renderizam no servidor e enviam HTML; nenhum JavaScript é enviado para o próprio componente. O conteúdo está na resposta inicial, sem lacuna de hidratação. Este é o melhor padrão para SEO. A interatividade vive em Client Components marcados com 'use client'.
  • Client-Side Rendering (CSR) — renderizado inteiramente no navegador. O Googlebot pode indexar após a onda de renderização (mediana de ~10 segundos, mas o percentil 90 se estende a horas), e outros rastreadores — Bingbot, bots de IA, bots de pré-visualização social — podem receber uma página vazia. No App Router, CSR é opt-in ('use client'); no Pages Router, evite buscar conteúdo principal em useEffect. Não use para conteúdo que você quer ranquear.

Um lembrete ao qual sempre volto: “Googlebot can render it” não é o mesmo que “you should make Googlebot render it.” Renderizar é caro, adiado e não é universal entre rastreadores.

A Metadata API (App Router)

A Metadata API é somente para Server Components — os metadados são resolvidos no servidor antes de a página renderizar, então eles chegam no HTML inicial. Exporte metadata de layout.js ou page.js:

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
}

Ou, quando as tags dependem de dados buscados, use generateMetadata():

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return { title: post.title, description: post.description }
}

Os campos que importam para SEO:

  • title — suporta uma string, um template ('%s | Brand'), um padrão e uma substituição absoluta. Defina o template uma vez no layout raiz e os títulos por página o herdam.
  • description, alternates.canonical (a maneira correta de definir uma canonical no App Router), openGraph (as imagens devem resolver para URLs absolutas), twitter (também usado nas pré-visualizações do LinkedIn e do Slack) e robots (index/follow além de diretivas específicas do googleBot, como max-snippet, max-image-preview).
  • metadataBaseobrigatório para que as URLs canônicas e de imagem OG sejam resolvidas corretamente. Esquecer isso é o erro de metadados mais comum no Next.js: URLs relativas vazam para suas tags canônicas e Open Graph, quebrando pré-visualizações sociais e confundindo os sinais canônicos.

Um exemplo de template de título:

// app/layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: { template: '%s | Brand Name', default: 'Brand Name' },
}
// app/blog/page.tsx
export const metadata: Metadata = { title: 'My Blog Post' }
// Output: <title>My Blog Post | Brand Name</title>

Duas pegadinhas. Primeiro, os metadados são mesclados superficialmente do layout para a página — um objeto aninhado como openGraph definido em um segmento filho substitui o do pai por completo, então um openGraph: { title: 'Home' } no nível da página descarta silenciosamente qualquer openGraph.images definido no layout. Segundo — e esta pega muita gente — metadata só funciona em Server Components. Exporte de um arquivo 'use client' e ele silenciosamente não faz nada.

Streaming metadata. Para páginas renderizadas dinamicamente, o generateMetadata pode transmitir os metadados após o HTML inicial. O Googlebot executa JavaScript e inspeciona o DOM completo, então metadados transmitidos funcionam para o Google. Mas o Next.js detecta “bots limitados a HTML” — Bingbot, Twitterbot, Slackbot, facebookexternalhit — e envia a eles metadados bloqueantes no <head>. Conforme a documentação do Next.js, “streaming metadata is disabled for bots and crawlers that expect metadata to be in the <head> tag.” (tradução) «o streaming de metadados é desativado para bots e rastreadores que esperam metadados na tag <head>». Isso é automático; nenhuma configuração é necessária. É um detalhe que quase nenhum guia concorrente cobre, e é por isso que o streaming de metadados não é um risco para os bots que não podem esperar por ele. Páginas pré-renderizadas são um caso completamente diferente — os metadados ali são resolvidos no momento da compilação, então não há stream com o qual se preocupar. Esse comportamento é específico da versão (atual a partir do Next.js 16.2.10); verifique novamente a documentação do generateMetadata ao atualizar. E como os caminhos de entrega diferem por ponto de entrada, verifique os metadados de duas maneiras, não apenas uma: uma solicitação direta/de produção (curl -I ou View Source) e uma navegação no lado do cliente para a mesma rota — o head pode ser atualizado de forma diferente entre as duas.

Evidence for this claim Prerendered Next.js pages do not use streaming metadata because metadata is resolved at build time in the documented path. Scope: route output, metadata and deployment Confidence: high · Verified: Metadata and OG images

Metadados com next/head (Pages Router)

No Pages Router, os metadados ficam em <Head> de next/head:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My Page | Brand</title>
        <meta name="description" content="Description" />
        <link rel="canonical" href="https://example.com/my-page" />
      </Head>
      {/* page content */}
    </>
  )
}

Defina title e description por página (não apenas em _app.js) e coloque uma canonical em todas as páginas, incluindo variantes paginadas. O pacote next-seo padroniza isso com um componente <NextSeo> e auxiliares de dados estruturados. Migrar para o App Router significa principalmente trocar next/head e next-seo pelo export nativo metadata.

Você precisa do pacote next-seo? É um plugin de terceiros (não faz parte do Next.js em si) — mantido ativamente, na v7.2,0 no momento em que este texto foi escrito, não arquivado. A própria documentação é explícita sobre onde ele se encaixa: para meta tags padrão no App Router, o README do pacote recomenda usar o export generateMetadata/metadata nativo do Next.js em vez de <NextSeo>; no Pages Router, <NextSeo> ainda é uma camada de conveniência razoável sobre next/head. O único caso de uso do App Router que o pacote ainda cobre são seus componentes auxiliares de JSON-LD (ArticleJsonLd, FAQPageJsonLd, etc. com useAppDir), que algumas equipes preferem em vez de criar manualmente <script type="application/ld+json">. Resumindo: em uma nova compilação do App Router, use primeiro a API de Metadados nativa — o pacote é opcional, não um requisito, e os próprios mantenedores dizem isso.

Sitemaps

No App Router, app/sitemap.ts é uma convenção de arquivo que gera /sitemap.xml:

import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: 'https://acme.com', lastModified: new Date(), priority: 1 },
    { url: 'https://acme.com/blog', lastModified: new Date(), priority: 0.8 },
  ]
}

Para sites grandes, generateSitemaps() divide em vários arquivos (o limite do Google é de 50 000 URLs por sitemap), cada um servido em /.../sitemap/[id].xml. A saída do sitemap também suporta sitemaps de imagem, sitemaps de vídeo e alternates.languages localizados. No Pages Router, use next-sitemap ou gere pages/sitemap.xml.js com getServerSideProps.

robots.txt

app/robots.ts gera seu arquivo robots programaticamente:

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [{ userAgent: '*', allow: '/', disallow: '/private/' }],
    sitemap: 'https://acme.com/sitemap.xml',
  }
}

Regras por user-agent e múltiplos sitemaps são suportados. O Pages Router usa um public/robots.txt estático. A regra que você não pode errar em nenhum dos roteadores: nunca desautorize seu JavaScript ou CSS — o Google não renderiza a partir de arquivos bloqueados, e em um framework JS isso pode esvaziar a página inteira.

next/image e Core Web Vitals

next/image é uma das razões mais fortes para usar o framework para SEO. Ele carrega preguiçosamente imagens abaixo da dobra, exige width/height (ou fill) para reservar espaço e evitar mudança de layout / CLS, serve WebP/AVIF automaticamente e emite um srcset adequado a partir da prop sizes. A otimização mais importante de CWV é a prop priority na sua imagem hero / acima da dobra, que a pré-carrega para um LCP mais rápido:

<Image src="/hero.jpg" width={1200} height={630} priority alt="Hero" />

Esquecer o priority na imagem LCP é o erro mais comum de CWV em Next.js — e os problemas de CWV são generalizados em sites Next.js reais (veja a aba Stats para os dados da Salt Agency). O alt é obrigatório: vazio para imagens decorativas, descritivo para imagens de conteúdo.

O next/link renderiza âncoras <a href> padrão no HTML, então o Google as segue normalmente, e ele adiciona navegação no lado do cliente além de pré-busca em segundo plano de links visíveis na viewport em produção. A regra de SEO é simples: use next/link para links internos e nunca substitua por um manipulador onClick ou navegação JavaScript que não produza uma âncora real — esses links não são rastreáveis. Use prefetch={false} em links de baixo valor para economizar largura de banda, se necessário.

Rotas dinâmicas e generateStaticParams

generateStaticParams() informa ao Next.js quais rotas dinâmicas devem ser pré-renderizadas no momento do build:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

Páginas construídas dessa forma são HTML totalmente estático — o melhor para SEO. Sem isso, as rotas dinâmicas são renderizadas sob demanda (SSR) por padrão, o que é aceitável, mas reintroduz latência do servidor. Combine com revalidate (ISR) para conteúdo que é atualizado regularmente. Certifique-se de que todas as URLs dinâmicas importantes estejam em generateStaticParams para que nada fique esperando na fila de renderização.

Dados estruturados (JSON-LD)

A Metadata API não tem campo de dados estruturados — você injeta JSON-LD como um <script> em um Server Component, o que o mantém no HTML renderizado no servidor sem custo no bundle do cliente:

const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', /* … */ }
return <script type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />

Article/BlogPosting, BreadcrumbList, Product e FAQPage são os tipos usuais. Valide com o Rich Results Test após qualquer alteração de renderização.

Erros comuns de SEO em Next.js

Baseado em auditorias reais e nos padrões acima:

  1. Canônicas ausentes ou relativas — frequentemente por um metadataBase esquecido, que também quebra as URLs de imagem OG.
  2. Sem priority na imagem LCP — a maior falha de CWV.
  3. Views de 404 retornando 200 — use o notFound() embutido para retornar um status real; soft 404s são comuns em sites Next.js.
  4. metadata exportado de um Client Component — silenciosamente não faz nada; é apenas para Server Component.
  5. CSR para conteúdo principal — buscar conteúdo crítico em useEffect significa que crawlers não-Google recebem páginas vazias.
  6. Roteamento por hash (#) em vez da History API — essas views não são rastreáveis separadamente.
  7. Não exportar generateStaticParams — rotas dinâmicas são renderizadas sob demanda em vez de serem pré-construídas.
  8. openGraph sobrescrito pela herança de layout — segmentos filhos substituem, não mesclam.
  9. JS/CSS bloqueados em robots.txt ou via Content Security Policy que impede o Chrome headless do Googlebot de carregar scripts — teste com URL Inspection.

Notas de implantação

Next.js é desenvolvido pela Vercel; hospedar lá oferece integração estreita (CDN de borda para páginas estáticas e ISR, bom TTFB), mas não é obrigatório. Defina redirecionamentos permanentes em redirects() no next.config.js (retorna 308, ou 301 com permanent: true) para sinalização confiável aos crawlers, defina cabeçalhos de segurança e cache via headers() e use cabeçalhos de resposta X-Robots-Tag para regras de noindex baseadas em caminho quando os metadados robots por página forem inconvenientes.

O que verificar onde. Estado de cache, códigos de status, redirecionamentos e metadados transmitidos não aparecem todos no mesmo teste — uma rota pode parecer correta em uma verificação e ainda estar quebrada em outra:

VerificaçãoOnde procurarPor que pode diferir do que você vê renderizado
Idade do cache/revalidaçãoCabeçalhos de resposta em uma solicitação direta (curl -I)O ISR pode servir uma página obsoleta na solicitação logo após a janela expirar
Status HTTP diretocurl -I na URL de produção, não na UI renderizadaUma view de “não encontrado” sem notFound() ainda retorna 200
Comportamento de redirecionamentoO contexto real que o aciona — redirect() em uma Server Action, um Route Handler, vs. um onClick no clienteO código de status e o caminho de resposta diferem pelo contexto de invocação, não apenas pelo destino
Metadados transmitidosSolicitação direta e navegação no lado do cliente para a mesma rotaClientes comuns podem obter metadados transmitidos; bots limitados a HTML obtêm metadados de bloqueio; os dois caminhos não são idênticos
Transições no lado do clienteNavegue no aplicativo e verifique novamente o <head>Uma rota correta no primeiro carregamento pode divergir após uma transição no cliente

Nada disso é garantido pelo framework — o Next.js fornece os mecanismos (redirects(), notFound(), revalidação, streaming), mas chaves de cache, invalidação, estado de preview e configuração de implantação ainda são de sua responsabilidade acertar e testar em produção, não apenas localmente.

Uma última nota sobre renderização dinâmica — servir HTML pré-renderizado para bots e JavaScript para usuários. O Google a descontinuou como recomendação: “dynamic rendering was a workaround and not a long-term solution.” (tradução) «a renderização dinâmica era uma solução alternativa e não uma solução de longo prazo.» Você não precisa dela no Next.js de qualquer forma — SSR, SSG, ISR e Server Components colocam todo o conteúdo no HTML nativamente. Mencione-a para reconhecê-la em uma auditoria; não construa com base nela.

O caminho feliz: App Router + Server Components + ISR + a Metadata API (com metadataBase) + next/image com priority. Acertando isso, a maior parte do SEO do Next.js está resolvida.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.