Checklist de SEO Técnico para Sites em Next.js (App Router)
Checklist verificável de SEO técnico para Next.js com App Router: renderização, metadata, canonical, sitemap, robots, Schema.org e Core Web Vitals.
· 10 min de leitura
Next.js resolve boa parte do SEO técnico por padrão, e é por isso que tantos sites em Next.js chegam à auditoria com os mesmos cinco problemas: canonical apontando para o preview da Vercel, sitemap sem as páginas dinâmicas, metadata duplicada entre layout e page, JSON-LD que não bate com o texto visível e imagem de herói sem prioridade. O framework dá as ferramentas, mas não configura o site por você.
Este checklist é o que aplicamos em todo projeto Next.js que entregamos, incluindo este site. Cada item tem o que verificar, como verificar e a API do framework envolvida. Ele assume App Router e Next.js 15 ou 16, e as APIs foram conferidas na documentação oficial do generateMetadata e do sitemap em setembro de 2026. Se você usa Pages Router, os conceitos valem, mas as APIs são outras.
1. Renderização: o HTML precisa chegar pronto
O Googlebot executa JavaScript, mas com fila e atraso. Crawlers de IA (GPTBot, ClaudeBot, PerplexityBot) executam pouco ou nada. A regra é simples: o conteúdo que importa para SEO precisa estar no HTML da resposta inicial, não montado depois no navegador.
No App Router, Server Components entregam isso por padrão. O problema aparece quando o time marca "use client" no componente que contém o texto principal, ou quando o conteúdo depende de useEffect para buscar dados.
Como verificar:
curl -s https://seusite.com.br/pagina | grep -c "<h1"
Se retornar zero, o H1 não está no HTML servido. Faça o mesmo para o parágrafo de abertura e para os dados estruturados. Outra opção é desligar o JavaScript no navegador e recarregar: o que sumir não existe para crawler de IA.
Checklist:
- H1, texto principal e FAQ estão em Server Components ou são passados como props para Client Components (o HTML ainda é gerado no servidor)
- Nenhum conteúdo indexável depende de
useEffectou de fetch no cliente - Páginas de conteúdo usam geração estática (
generateStaticParams) ou cache com revalidação, nãodynamic = "force-dynamic"sem motivo -
loading.tsxnão substitui o conteúdo por skeleton no HTML inicial de páginas estáticas
2. Metadata: título, descrição e a armadilha do layout
A Metadata API substitui o next/head. O que quebra na prática é a herança: metadata definida no layout.tsx raiz vale para todas as páginas que não a sobrescrevem, e é assim que dezenas de páginas acabam com o mesmo título.
Checklist:
- Toda página tem
titleedescriptionpróprios, viaexport const metadata(estático) ougenerateMetadata(dinâmico) - O layout raiz define
title.template(por exemplo"%s | Anixt") etitle.default, e as páginas passam só o título específico -
metadataBaseestá definido no layout raiz com a URL de produção, para que caminhos relativos emcanonicaleopenGraph.imagesresolvam corretamente - Títulos têm até cerca de 60 caracteres e descrições até cerca de 155, para não serem cortados na busca
-
openGraphetwitterestão preenchidos, com imagem gerada poropengraph-image.tsxquando a página é dinâmica
Exemplo mínimo de página dinâmica:
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = getPostBySlug(slug)
if (!post) return {}
return {
title: post.seoTitle ?? post.title,
description: post.description,
alternates: { canonical: `/blog/${slug}` },
}
}
Note o await params: a partir do Next.js 15, params é uma Promise. Código migrado de versões anteriores que acessa params.slug direto compila com aviso e pode gerar metadata vazia.
3. Canonical: uma URL por conteúdo
Canonical errado é o defeito mais frequente em site Next.js hospedado na Vercel. Sem metadataBase, ou com ele lendo VERCEL_URL, o canonical de produção aponta para projeto-abc123.vercel.app, e o Google passa a tratar o domínio de preview como original.
Checklist:
-
metadataBaseusa o domínio de produção fixo, nãoprocess.env.VERCEL_URL - Toda página indexável declara
alternates.canonicalcom caminho relativo - Páginas com parâmetros de consulta (filtros, UTM, paginação) apontam o canonical para a versão limpa
- Deploys de preview têm
X-Robots-Tag: noindexou usam a proteção de deploy da Vercel, para que nunca sejam indexados -
wwwe não-wwwredirecionam para uma única versão com 308 (configure emnext.configou no provedor de DNS) - Barra final é consistente (
trailingSlashnonext.config), sem duas URLs servindo o mesmo conteúdo
Como verificar: abra o HTML de produção e confira o <link rel="canonical">. Depois, no Search Console, use a inspeção de URL e compare "URL canônica declarada pelo usuário" com "URL canônica selecionada pelo Google". Se divergem, o Google não aceitou a sua.
4. Sitemap e robots como código
No App Router, app/sitemap.ts e app/robots.ts geram os arquivos em build. A vantagem é que o sitemap pode ler a mesma fonte de dados das páginas. O erro comum é o sitemap listar só as rotas estáticas e esquecer as dinâmicas (posts, cidades, serviços).
Checklist:
-
sitemap.tsinclui todas as páginas indexáveis, incluindo as geradas porgenerateStaticParams -
lastModifiedreflete a data real de alteração do conteúdo, nãonew Date()em toda build (isso faz o Google ignorar o campo) - Sites com mais de 50 mil URLs usam
generateSitemapspara dividir em múltiplos arquivos -
robots.tsaponta para o sitemap e bloqueia apenas o que não deve ser indexado (/api/,/obrigado/, áreas logadas) -
robots.tsnão bloqueia crawlers de IA por engano; veja a lista de user-agents em como aparecer no ChatGPT, Gemini e Perplexity - Páginas que não devem indexar (obrigado, busca interna, rascunho) usam
robots: { index: false }na metadata, não só o robots.txt
Exemplo de sitemap com páginas dinâmicas:
import type { MetadataRoute } from "next"
export default function sitemap(): MetadataRoute.Sitemap {
const posts = getAllPosts().map((post) => ({
url: `https://seusite.com.br/blog/${post.slug}`,
lastModified: post.updatedAt ?? post.publishedAt,
changeFrequency: "monthly" as const,
}))
return [{ url: "https://seusite.com.br", lastModified: "2026-09-01" }, ...posts]
}
5. Dados estruturados: JSON-LD que bate com a página
O Google é claro no seu guia sobre recursos de IA: os dados estruturados precisam corresponder ao texto visível. Schema de FAQ com perguntas que não aparecem na página, ou LocalBusiness com telefone diferente do rodapé, é ignorado ou penalizado.
No Next.js, JSON-LD entra como <script type="application/ld+json"> renderizado no servidor, dentro do componente da página. Não use next/script com estratégia afterInteractive para isso, porque o script só entra no DOM depois da hidratação.
Checklist:
-
Organization(ouLocalBusiness) com@idestável no layout raiz, referenciado pelas outras entidades -
WebSitecom@idepublisherapontando para a organização -
BreadcrumbListem toda página abaixo da home, coerente com a navegação visível -
ArticleouBlogPostingem posts, comauthordo tipoPerson,datePublishededateModifiedreais -
Serviceem páginas de serviço,FAQPagesó onde a FAQ está visível no HTML - Tipos específicos quando existem:
AutoRepair,MedicalBusiness,LegalService,Dentist - Dados sensíveis (telefone, endereço, horário) vêm da mesma constante usada no rodapé, para não divergirem
- Validado no teste de pesquisa aprimorada do Google e no validador do Schema.org
6. Core Web Vitals: os limites e onde o Next.js falha
Os limites oficiais, segundo a documentação do Google Search Central, continuam os mesmos em 2026: LCP até 2,5 s, INP até 200 ms, CLS até 0,1, medidos no percentil 75 de usuários reais. Artigos que citam LCP de 2,0 s ou INP de 150 ms não apontam fonte do Google.
Next.js entrega bons números por padrão, e os problemas costumam vir de decisões do projeto:
| Métrica | Causa comum em Next.js | Correção |
|---|---|---|
| LCP alto | Imagem de herói sem priority, fonte externa bloqueando render, componente de herói em Client Component pesado | priority no next/image do herói, next/font com display: swap, herói em Server Component |
| CLS alto | next/image sem width/height ou fill sem container dimensionado, banner de cookies inserido depois do carregamento, fonte trocando de tamanho | Dimensões explícitas, espaço reservado para elementos tardios, size-adjust via next/font |
| INP alto | Hidratação de árvore grande de Client Components, biblioteca de animação rodando na thread principal, handlers pesados em scroll | Reduzir "use client" ao mínimo, dynamic() com ssr: false para widgets não críticos, requestIdleCallback para trabalho não urgente |
Checklist:
- Imagem de herói usa
next/imagecompriorityesizescorreto - Fontes carregam por
next/font(self-hosted, sem requisição externa em tempo de render) - Nenhum script de terceiros (chat, analytics, pixel) carrega com
beforeInteractivesem necessidade - Bundle de cliente da página inicial abaixo de 200 kB comprimido (verifique com
@next/bundle-analyzer) - Dados de campo no Search Console (relatório Core Web Vitals) e no PageSpeed Insights estão verdes, não só o Lighthouse local
7. Estrutura, links internos e o resto que o crawler lê
- Um único
<h1>por página, e ele é o título do conteúdo, não o logo - Hierarquia
h2eh3sem pular níveis - Links internos usam
next/linkcomhrefreal (nãoonClickcomrouter.push), para que apareçam no HTML - Toda página indexável recebe ao menos um link interno de outra página; páginas órfãs não ranqueiam
- Breadcrumb visível e coerente com a URL
-
not-found.tsxretorna status 404 real (o App Router faz isso ao chamarnotFound()), e páginas removidas redirecionam com 301 ou 308 vianext.configoumiddleware - Imagens de conteúdo têm
altdescritivo; imagens decorativas têmalt="" -
lang="pt-BR"no<html>do layout raiz
8. Verificação final antes do deploy
Um roteiro de dez minutos que pega a maioria dos problemas:
curlda home e de uma página dinâmica: H1, canonical e JSON-LD presentes no HTML/sitemap.xmle/robots.txtabrindo em produção, com as URLs de produção- Inspeção de URL no Search Console para uma página nova, confirmando indexação permitida e canonical aceito
- PageSpeed Insights em mobile para home e página de serviço mais importante
- Teste de pesquisa aprimorada para uma página com FAQ e uma com
Article - Busca
site:seudominio.com.brno Google para conferir se URLs de preview ou parâmetros estranhos foram indexados
Perguntas frequentes
Next.js é melhor para SEO do que WordPress?
Não por si só. Next.js dá controle total sobre HTML, performance e dados estruturados, o que permite chegar a um resultado técnico melhor. Mas exige que alguém implemente cada item deste checklist. WordPress com tema leve e plugin de SEO configurado chega a um resultado aceitável com menos trabalho. A diferença aparece em escala, performance e em projetos com páginas programáticas, como as Paginações SEO.
Preciso de generateStaticParams em todas as rotas dinâmicas?
Para rotas de conteúdo que você quer indexadas, sim, ou ao menos de cache com revalidação. Páginas geradas sob demanda sem cache respondem mais devagar ao crawler e consomem orçamento de rastreamento. Rotas que dependem de usuário logado não precisam, porque não devem ser indexadas.
O App Router indexa pior que o Pages Router?
Não. O HTML gerado é equivalente ou melhor, porque Server Components enviam menos JavaScript. Os problemas atribuídos ao App Router quase sempre vêm de "use client" em excesso ou de metadata herdada do layout sem sobrescrita.
Como sei se crawlers de IA estão conseguindo ler o site?
Verifique nos logs do servidor ou do provedor as requisições com user-agent GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot e Google-Extended, e confira se retornam 200 com o HTML completo. Se o site está atrás de proteção contra bots, confira se esses agentes não estão sendo bloqueados na camada de rede, o que o robots.txt não mostra.
A Anixt aplica este checklist em todo projeto de desenvolvimento em Next.js e em auditorias de sites existentes. Se quer o resultado da verificação para o seu site, com o que passa e o que falha em cada item, é esse o ponto de partida.