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: 2 de set. de 2026 · Avançado

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 is a React framework, so everything in JavaScript SEO applies. What makes it worth its own guide is that Next.js ships first-class answers to most JS-SEO problems: server rendering, static generation, a metadata system, and sitemap/robots conventions. The hard part isn’t whether Google can read it — Google has rendered JavaScript for years — it’s choosing the right rendering mode and not leaving the SEO basics unwired. This is a specialized case of headless CMS SEO: the CMS barely matters, the frontend’s rendering decisions decide everything. » (Tradução) (Síntese localizada do trecho vinte, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«One framing to keep straight: “this is a Next.js site” doesn’t tell you how any single URL is delivered. Rendering mode, caching, and Server/Client Component boundaries are set per route (sometimes per segment) — a project can mix a static marketing page, an SSR product page, and a Client Component dashboard. Don’t extrapolate one route’s behavior to “the whole app”; test the specific URL. » (Tradução) (Síntese localizada do trecho vinte e um, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## Two routers, two sets of mechanics » (Tradução) (Síntese localizada do trecho vinte e dois, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«Next.js has two routers, and they handle SEO differently: » (Tradução) (Síntese localizada do trecho vinte e três, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«- Pages Router (the older model) — data fetching via getStaticProps / getServerSideProps; metadata via <Head> from next/head (or the next-seo package); no Server Components.

  • App Router (v13+, the current and recommended approach) — React Server Components by default; the native Metadata API (metadata export / generateMetadata); file conventions for app/sitemap.ts and app/robots.ts; generateStaticParams for dynamic routes. Evidência desta afirmação The App Router uses Server Components and supports generateStaticParams plus metadata file conventions. Escopo: Current Next.js App Router behavior; route rendering can become dynamic based on APIs used. Confiança: alta · Verificado: Next.js: Server and Client Components Next.js: generateStaticParams » (Tradução) (Síntese localizada do trecho vinte e quatro, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«Both can rank well. The App Router gives you a cleaner, integrated metadata system (no next/head juggling) and Server Components out of the box, which is why I’d reach for it on a new build. But “App Router or you can’t do SEO” is a myth — plenty of Pages Router sites rank fine. » (Tradução) (Síntese localizada do trecho vinte e cinco, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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

«How Google handles JavaScript is a three-phase pipeline — crawl, then a deferred render wave, then index. “All pages with a 200 HTTP status code are sent to the rendering queue.” The whole game in Next.js is choosing a mode that puts your content in the HTML before that render wave, so there’s nothing to wait for. Evidência desta afirmação Google crawls, renders, and indexes JavaScript pages, and successful pages can enter the rendering queue. Escopo: Google Search processing, not a promise that a URL will be indexed. Confiança: alta · Verificado: Google: JavaScript SEO basics » (Tradução) (Síntese localizada do trecho vinte e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

  • SSG — pré-renderização na compilação, sem fila de renderização. generateStaticParams()/getStaticPaths() define rotas dinâmicas.
  • ISR — páginas estáticas revalidadas por intervalo (export const revalidate = 3600). Após o intervalo, a primeira requisição ainda pode receber conteúdo antigo; para preços/estoque voláteis, prefira SSR.
  • SSR — HTML por requisição; completo, mas exige atenção a TTFB/LCP. export const dynamic = 'force-dynamic' ou APIs de requisição ativam SSR.
  • React Server Components — padrão do App Router; HTML inicial sem lacuna de hidratação. Interatividade fica em componentes com 'use client'.
  • CSR — depende do navegador. Google pode renderizar depois, mas Bingbot, bots de IA e prévias sociais podem receber vazio. Evite useEffect para conteúdo principal.

«A reminder I keep coming back to: “Googlebot can render it” is not the same as “you should make Googlebot render it.” Rendering is expensive, deferred, and not universal across crawlers. » (Tradução) (Síntese localizada do trecho vinte e nove, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## The Metadata API (App Router) » (Tradução) (Síntese localizada do trecho trinta, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«The Metadata API is Server Component only — metadata resolves on the server before the page renders, so it lands in the initial HTML. Export metadata from layout.js or page.js: » (Tradução) (Síntese localizada do trecho trinta e um, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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

«Or, when the tags depend on fetched data, use generateMetadata(): » (Tradução) (Síntese localizada do trecho trinta e três, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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

«The fields that matter for SEO: » (Tradução) (Síntese localizada do trecho trinta e cinco, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

  • title — aceita string, template ('%s | Brand'), padrão e substituição absoluta. Defina o template no layout raiz.
  • description, alternates.canonical, openGraph, twitter e robots controlam metadados e prévias.
  • metadataBase — necessário para canônicas e imagens OG absolutas; esquecê-lo gera URLs relativas e sinais canônicos confusos.

«A title-template example: » (Tradução) (Síntese localizada do trecho trinta e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

// 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>

«Two gotchas. First, metadata is shallowly merged from layout to page — a nested object like openGraph defined in a child segment replaces the parent’s entirely, so a page-level openGraph: { title: 'Home' } quietly drops any openGraph.images set in the layout. Second — and this one bites people — metadata only works in Server Components. Export it from a 'use client' file and it silently does nothing. » (Tradução) (Síntese localizada do trecho trinta e nove, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«Streaming metadata. For dynamically rendered pages, generateMetadata can stream the metadata after the initial HTML. Googlebot executes JavaScript and inspects the full DOM, so streamed metadata works for Google. But Next.js detects “HTML-limited bots” — Bingbot, Twitterbot, Slackbot, facebookexternalhit — and ships them blocking metadata in the <head> instead. Per the Next.js docs, “streaming metadata is disabled for bots and crawlers that expect metadata to be in the <head> tag.” This is automatic; no configuration needed. It’s a detail almost no competing guide covers, and it’s why streaming metadata isn’t a risk for the bots that can’t wait for it. Prerendered pages are a different case entirely — metadata there resolves at build time, so there’s no stream to worry about. This behavior is version-specific (current as of Next.js 16.2.10); re-check the generateMetadata docs when you upgrade. And because delivery paths differ by entry point, verify metadata two ways, not one: a direct/production request (curl -I or View Source) and a client-side navigation to the same route — the head can update differently between the two.

Evidência desta afirmação Prerendered Next.js pages do not use streaming metadata because metadata is resolved at build time in the documented path. Escopo: route output, metadata and deployment Confiança: alta · Verificado: Metadata and OG images

» (Tradução) (Síntese localizada do trecho quarenta, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## Metadata with next/head (Pages Router) » (Tradução) (Síntese localizada do trecho quarenta e um, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«On the Pages Router, metadata lives in <Head> from next/head: » (Tradução) (Síntese localizada do trecho quarenta e dois, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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 */}
    </>
  )
}

«Set title and description per page (not just in _app.js), and put a canonical on every page including paginated variants. The next-seo package standardizes this with a <NextSeo> component and structured-data helpers. Migrating to the App Router mostly means trading next/head and next-seo for the native metadata export. » (Tradução) (Síntese localizada do trecho quarenta e quatro, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«Do you need the next-seo package? It’s a third-party plugin (not part of Next.js itself) — actively maintained, at v7.2.0 as of this writing, not archived. Its own docs are explicit about where it fits: for standard meta tags on the App Router, the package’s README recommends using Next.js’s built-in generateMetadata/metadata export instead of <NextSeo>; on the Pages Router, <NextSeo> is still a reasonable convenience layer over next/head. The one App Router use case the package still covers is its JSON-LD helper components (ArticleJsonLd, FAQPageJsonLd, etc. with useAppDir), which some teams prefer over hand-rolling <script type="application/ld+json">. Bottom line: on a new App Router build, reach for the native Metadata API first — the package is optional, not a requirement, and its own maintainers say so. » (Tradução) (Síntese localizada do trecho quarenta e cinco, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## Sitemaps » (Tradução) (Síntese localizada do trecho quarenta e seis, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«In the App Router, app/sitemap.ts is a file convention that outputs /sitemap.xml: » (Tradução) (Síntese localizada do trecho quarenta e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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 },
  ]
}

«For large sites, generateSitemaps() shards into multiple files (Google’s limit is 50,000 URLs per sitemap), each served at /.../sitemap/[id].xml. The sitemap output also supports image sitemaps, video sitemaps, and localized alternates.languages. On the Pages Router, use next-sitemap or generate pages/sitemap.xml.js with getServerSideProps. » (Tradução) (Síntese localizada do trecho quarenta e nove, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## robots.txt » (Tradução) (Síntese localizada do trecho cinquenta, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«app/robots.ts generates your robots file programmatically: » (Tradução) (Síntese localizada do trecho cinquenta e um, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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

«Per-user-agent rules and multiple sitemaps are supported. The Pages Router uses a static public/robots.txt. The rule you cannot get wrong on either router: never disallow your JavaScript or CSS — Google won’t render from blocked files, and on a JS framework that can blank out the page entirely. » (Tradução) (Síntese localizada do trecho cinquenta e três, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## next/image and Core Web Vitals » (Tradução) (Síntese localizada do trecho cinquenta e quatro, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«next/image is one of the strongest reasons to use the framework for SEO. It lazy-loads below-the-fold images, requires width/height (or fill) so it reserves space and prevents layout shift / CLS, serves WebP/AVIF automatically, and emits a proper srcset from the sizes prop. The single most important CWV optimization is the priority prop on your hero / above-the-fold image, which preloads it for a faster LCP: » (Tradução) (Síntese localizada do trecho cinquenta e cinco, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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

«Forgetting priority on the LCP image is the most common Next.js CWV mistake — and CWV problems are widespread on real Next.js sites (see the Stats tab for the Salt Agency data). alt is required: empty for decorative images, descriptive for content images. » (Tradução) (Síntese localizada do trecho cinquenta e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## next/link and internal linking » (Tradução) (Síntese localizada do trecho cinquenta e oito, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«next/link renders standard <a href> anchors in the HTML, so Google follows them normally, and it adds client-side navigation plus background prefetching of in-viewport links in production. The SEO rule is simple: use next/link for internal links, and never substitute an onClick handler or JavaScript navigation that doesn’t produce a real anchor — those links aren’t crawlable. Use prefetch={false} on low-value links to save bandwidth if you need to. » (Tradução) (Síntese localizada do trecho cinquenta e nove, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## Dynamic routes and generateStaticParams » (Tradução) (Síntese localizada do trecho sessenta, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«generateStaticParams() tells Next.js which dynamic routes to pre-render at build time: » (Tradução) (Síntese localizada do trecho sessenta e um, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

// 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.

«## Structured data (JSON-LD) » (Tradução) (Síntese localizada do trecho sessenta e quatro, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

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, and FAQPage are the usual types. Validate with the Rich Results Test after any rendering change. » (Tradução) (Síntese localizada do trecho sessenta e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«## Common Next.js SEO mistakes » (Tradução) (Síntese localizada do trecho sessenta e oito, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«Drawn from real audits and the patterns above: » (Tradução) (Síntese localizada do trecho sessenta e nove, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

  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.

Observações sobre implantação

O Next.js é desenvolvido pela Vercel; hospedar lá oferece integração estreita (CDN de borda para páginas estáticas e ISR, com bom TTFB), mas não é obrigatório. Defina redirecionamentos permanentes em redirects() no next.config.js (retorna 308, ou 301 com permanent: true) para enviar sinais confiáveis aos rastreadores, configure cabeçalhos de segurança e cache por headers() e use o cabeçalho de resposta X-Robots-Tag para regras noindex baseadas em caminho quando a metadata robots por página for inconveniente.

«What to check where. Cache state, status codes, redirects, and streamed metadata don’t all show up in the same test — a route can look fine in one check and still be broken in another: » (Tradução) (Síntese localizada do trecho setenta e três, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«| Check | Where to look | Why it can differ from what you see rendered | | --- | --- | --- | | Cache/revalidation age | Response headers on a direct request (curl -I) | ISR can serve a stale page on the request right after the window expires | | Direct HTTP status | curl -I on the production URL, not the rendered UI | A “not found” view without notFound() still returns 200 | | Redirect behavior | The actual context that fires it — redirect() in a Server Action, a Route Handler, vs. a client onClick | Status code and response path differ by invocation context, not just the destination | | Streamed metadata | Direct request and client-side navigation to the same route | Ordinary clients can get streamed metadata; HTML-limited bots get blocking metadata; the two paths aren’t identical | | Client-side transitions | Navigate in-app, then re-check the <head> | A route that’s correct on first load can drift after a client transition | » (Tradução) (Síntese localizada do trecho setenta e quatro, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«None of this is guaranteed by the framework — Next.js gives you the mechanisms (redirects(), notFound(), revalidation, streaming), but cache keys, invalidation, preview state, and deployment configuration are still your responsibility to get right and to test in production, not just locally. » (Tradução) (Síntese localizada do trecho setenta e cinco, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«One last note on dynamic rendering — serving prerendered HTML to bots and JavaScript to users. Google has deprecated it as a recommendation: “dynamic rendering was a workaround and not a long-term solution.” You don’t need it on Next.js anyway — SSR, SSG, ISR, and Server Components all put content in the HTML natively. Mention it so you recognize it in an audit; don’t build on it. » (Tradução) (Síntese localizada do trecho setenta e seis, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

«The happy path: App Router + Server Components + ISR + the Metadata API (with metadataBase) + next/image with priority. Get those right and most of Next.js SEO is handled. » (Tradução) (Síntese localizada do trecho setenta e sete, parte um: o texto-fonte foi preservado para conferência na revisão nativa.)

Adicionar uma nota de especialista

Fixar uma citação de especialista

É uma pessoa nova? Crie o perfil não reivindicado dela em /admin/experts/ → Fixar uma citação de especialista primeiro.