Next.js SEO: o Impacto da Renderização

No Next.js, a decisão mais importante para SEO não é o texto da página — é a forma como essa página é entregue. O framework oferece várias estratégias de renderização, e a que você escolhe para cada rota determina se um rastreador recebe o conteúdo pronto na primeira resposta do servidor ou se precisa executar JavaScript para descobrir que ele existe. Um bom título, uma boa estrutura de headings e um conteúdo bem escrito não adiantam se a página que chega ao Googlebot está vazia.
Este texto assume que você já conhece o básico de SEO técnico para JavaScript — como rastreadores lidam com conteúdo renderizado no cliente, por que isso é arriscado, o que é hidratação. O foco aqui é o que muda especificamente no Next.js: os modos de renderização disponíveis, a divisão entre App Router e Pages Router (e por que ela bagunça boa parte dos tutoriais que você vai encontrar), como o framework trata metadados, os erros que aparecem quando o projeto já está em produção, e como conferir, na prática, o que está sendo entregue.
Os quatro modos de renderização e a consequência real de cada um
O Next.js dá a você quatro formas de gerar o HTML de uma rota, e a escolha é feita página por página, não para o projeto inteiro. Entender a consequência prática de cada uma — não só o nome — é o que evita decisões erradas.
A regra que resolve a maioria das dúvidas nesse ponto é simples: qualquer conteúdo que só existe depois que o JavaScript do cliente roda é conteúdo que você está pedindo para o rastreador ir buscar por conta própria. O Googlebot consegue executar JavaScript, mas isso acontece numa etapa separada da primeira leitura da página, e nem todo rastreador — de outros buscadores, de ferramentas de terceiros, de bots que geram pré-visualizações em redes sociais — chega a dar esse segundo passo. Se uma informação importa para ranquear, ela deveria estar no HTML que o servidor entrega, não só no DOM depois da hidratação.
- Geração estática (SSG): o HTML é montado uma vez, no momento do build, e servido pronto para todo mundo. É a opção mais barata de rastrear, porque não há espera nem execução de script — o conteúdo já está lá na primeira resposta.
- Renderização no servidor (SSR): o HTML é montado a cada requisição, no servidor. O rastreador ainda recebe uma página pronta, mas o tempo de resposta depende do que o servidor precisa calcular ou buscar antes de responder.
- Regeneração estática incremental (ISR): a página é estática como no SSG, mas o Next.js pode reconstruí-la em segundo plano depois de um tempo definido ou sob demanda. Isso junta a velocidade do estático com dados que não ficam parados para sempre, ao custo de uma janela em que a página servida pode estar ligeiramente desatualizada.
- Renderização no cliente (CSR): o servidor entrega um HTML quase vazio, e o conteúdo real é montado por JavaScript depois de carregar no navegador. É a opção mais frágil para SEO, porque o rastreador precisa executar esse script para ver o que foi escrito.
App Router x Pages Router: por que o conselho que você lê pode não servir
O Next.js vive uma transição que ainda não terminou. O Pages Router (a pasta pages/) foi o modelo original, e boa parte do conteúdo mais antigo sobre SEO no Next.js — inclusive muito do que ainda aparece bem posicionado em buscas — foi escrito para ele. O App Router (a pasta app/) trouxe um modelo diferente de composição, com Server Components por padrão, uma forma própria de gerenciar metadata e convenções de arquivo, como sitemap.ts ou robots.ts, que simplesmente não existem no modelo antigo.
Isso importa na prática porque uma instrução como 'use next/head para definir o title da página' é correta no Pages Router e não tem efeito nenhum dentro de app/, onde metadata é tratada por um objeto exportado ou por uma função. Um tutorial que mistura os dois sem avisar — o que acontece com frequência, porque muito conteúdo foi escrito antes do App Router virar padrão — leva a copiar código que não faz nada na versão do seu projeto, sem nenhum erro no console para avisar.
Antes de aplicar qualquer recomendação técnica sobre Next.js, vale confirmar em qual dos dois roteadores o projeto está: a pasta na raiz do projeto (app/ ou pages/) já responde isso. Projetos migrados parcialmente podem ter as duas ao mesmo tempo, o que é motivo a mais para não assumir que uma dica se aplica sem checar onde a rota em questão realmente vive.
Metadados por rota: title, description, canonical e Open Graph
O Next.js trata metadata como algo que pertence à rota, não à aplicação inteira. No App Router isso normalmente é um objeto metadata exportado do arquivo da página ou — quando o título, a descrição ou a imagem dependem de dados que só existem em tempo de execução, como o nome de um produto vindo de uma API — uma função generateMetadata que roda antes da página ser montada e recebe os parâmetros da rota.
Isso é especialmente relevante em páginas templadas: uma rota como /produtos/[slug] serve centenas ou milhares de páginas diferentes a partir do mesmo componente, e cada uma precisa de um title, uma description e uma tag canonical que reflitam o item específico, não um valor genérico repetido em todas. Sem metadata dinâmica, é fácil publicar mil páginas com o mesmo title porque o valor foi escrito como texto fixo em vez de derivado do dado da rota.
Open Graph segue a mesma lógica: og:title, og:description e og:image podem — e, no caso de páginas templadas, devem — variar por rota. Vale conferir a tag canonical com atenção redobrada quando a mesma página é acessível por mais de um caminho, com e sem parâmetro de busca, com e sem barra final: sem uma canonical explícita e correta, o Next.js não resolve essa duplicação sozinho.
Os erros que aparecem depois que o site já está no ar
Uma parte considerável dos problemas de SEO em projetos Next.js não está na escolha de renderização em si, mas em detalhes que passam despercebidos até alguém notar que uma página não está indexada ou que aparece um título errado no resultado de busca.
- Soft 404: uma rota busca um registro que não existe (um produto removido, um slug errado) e, em vez de responder com status 404, renderiza uma página de 'não encontrado' com status 200. Sem o notFound() do Next.js, ou o retorno de status correto em getServerSideProps, o rastreador entende que aquela URL tem conteúdo válido para indexar — e passa a indexar uma página vazia.
- Título que não muda em navegação client-side: depois da primeira carga, o Next.js troca de rota sem recarregar a página inteira. Se o title depender de um mecanismo que só roda na primeira renderização, navegar entre páginas pelo lado do cliente pode deixar o title da rota anterior visível na aba do navegador, e em casos mal configurados também no HTML servido para quem não executa JavaScript.
- Problemas de hidratação: quando o HTML gerado no servidor não bate exatamente com o que o React monta no cliente — datas formatadas de forma diferente, conteúdo condicionado a window ou a localStorage — o React descarta parte da árvore e remonta, o que pode deixar o conteúdo momentaneamente diferente do que foi entregue na resposta inicial.
- Redirecionamentos implementados só no cliente: um useEffect que chama router.push() manda o usuário para outro lugar, mas o servidor já respondeu 200 com o conteúdo da página original. Um rastreador que não executa esse script nunca vê o redirecionamento — ele só existe para quem carrega e roda o JavaScript. Redirecionamento com valor de SEO precisa responder com status 3xx no servidor, via next.config ou middleware, não como efeito colateral no cliente.
- Sitemap desatualizado em sites estáticos: um sitemap gerado em build time, num site majoritariamente SSG, só reflete as URLs que existiam naquele build. Se novas páginas são adicionadas por ISR ou por conteúdo dinâmico depois do deploy, o sitemap.ts precisa ser montado de um jeito que também capture essas rotas — senão ele fica alguns passos atrás da realidade do site.
Imagens e Core Web Vitals: o que o framework resolve e o que continua sendo seu
O componente next/image resolve boa parte do trabalho mecânico de imagem: gera tamanhos diferentes para diferentes telas, adia o carregamento de imagens fora da área visível e evita o deslocamento de layout que acontece quando uma imagem carrega sem dimensões reservadas. Isso ajuda diretamente métricas de Core Web Vitals, que é um sinal de ranqueamento documentado pelo Google.
O que o componente não resolve sozinho é a imagem de LCP — a maior imagem visível assim que a página carrega, geralmente o hero de uma landing page ou a foto principal de um produto. Ela precisa ser marcada com a propriedade priority para carregar com prioridade alta em vez de ser adiada como as demais; sem isso, next/image aplica o mesmo carregamento tardio que usaria para qualquer outra imagem, e a métrica de LCP piora justamente na imagem que mais pesa nela.
Vale lembrar também que a otimização de imagem não é gratuita em tempo de execução quando a rota depende do otimizador do Next.js rodando a cada requisição nova — em alguns ambientes de hospedagem isso pode adicionar latência que consome parte do ganho. Testar o comportamento real em produção, e não só em desenvolvimento local, é o jeito mais confiável de saber se a imagem está de fato ajudando a métrica.
Rotas internacionalizadas: por que isso pesa mais para quem escreve em português
Um site em inglês normalmente serve um público e uma variante de idioma. Um site em português frequentemente precisa decidir entre pt-BR e pt-PT, e muitas vezes convive com outros idiomas na mesma aplicação — o que torna o roteamento internacional do Next.js uma peça de SEO técnico bem mais relevante do que costuma ser para quem escreve só em inglês.
O App Router não tem um sistema de i18n embutido do jeito que o Pages Router tinha, com a chave i18n em next.config; a abordagem recomendada hoje é estruturar as rotas com um segmento de idioma, como app/[lang]/, e montar manualmente a lógica de detecção e as tags hreflang correspondentes. Isso exige mais decisão do desenvolvedor, inclusive decidir se pt-BR e pt-PT dividem o mesmo conteúdo com hreflang apontando uma para a outra, ou se são conteúdos de fato distintos.
Independente da estrutura escolhida — subpastas por idioma, domínios separados ou subdomínios — cada versão de idioma precisa da própria tag canonical apontando para si mesma, e das tags hreflang listando as outras versões disponíveis, incluindo uma entrada x-default para quando o buscador não consegue determinar qual idioma servir. Um erro comum é gerar hreflang só na página inicial e esquecer as páginas internas, o que deixa o sinal de idioma incompleto justamente onde mais rotas existem.
Como verificar o que está sendo entregue, em vez de confiar no framework
A forma mais confiável de saber o que um rastreador recebe não é olhar a página no navegador — é pedir o HTML bruto, do jeito que um bot faria, e ler o que volta. Um curl simples na URL (curl -s https://seusite.com/rota) mostra exatamente o que o servidor respondeu antes de qualquer JavaScript rodar: se o título, a descrição, o conteúdo principal e as tags canonical e Open Graph já estão lá, ou se a resposta é um HTML mínimo esperando o script montar tudo depois.
Isso serve para pegar exatamente os problemas descritos antes: uma página com soft 404 que devolve HTML de erro com status 200 continua parecendo, no navegador, uma página comum — só olhando a resposta bruta, junto com o código de status, o problema fica óbvio. O mesmo vale para conferir se uma rota que deveria ser estática ou SSR está de fato entregando conteúdo pronto, em vez de ter regredido, sem ninguém notar, para renderização no cliente depois de uma mudança de código.
- Compare o HTML bruto (curl ou 'exibir código-fonte') com o que aparece inspecionando o DOM no navegador; uma diferença grande entre os dois é o sintoma mais direto de conteúdo que só existe depois da hidratação.
- Confira o cabeçalho de status HTTP, não só o conteúdo visual da página, em rotas que dependem de um registro externo — produto, post, usuário — que pode não existir mais.
- Repita a verificação depois de deploys que mudam a estratégia de renderização de uma rota; é fácil uma página que era estática virar, sem intenção, uma página client-side depois de uma refatoração.
Perguntas frequentes
Preciso migrar todas as rotas para o App Router por causa de SEO?
Não necessariamente. O Pages Router continua funcionando e sendo indexado normalmente — o que importa é a renderização escolhida para cada rota, não qual dos dois roteadores o projeto usa. A migração faz mais sentido quando o projeto já precisa das funcionalidades específicas do App Router, não só pelo argumento de SEO isoladamente.
SSR é sempre melhor que SSG para SEO?
Não. Para rastreamento, o que importa é que o HTML chegue pronto, e tanto SSG quanto SSR entregam isso. SSG costuma responder mais rápido, porque o HTML já está pronto e só precisa ser servido; SSR faz sentido quando o conteúdo muda a cada requisição de um jeito que o build estático não consegue acompanhar.
ISR pode prejudicar o SEO se a página ficar desatualizada entre uma regeneração e outra?
Pode ser um problema se o conteúdo mudar com frequência e a janela de revalidação for longa demais, deixando informação errada visível por um tempo. Para conteúdo que muda pouco, essa janela raramente chega a importar; para conteúdo sensível a tempo, como preço ou disponibilidade, vale revalidar sob demanda em vez de confiar só num intervalo fixo.
Como sei se uma página do meu site em Next.js está sendo renderizada no cliente sem eu perceber?
Peça o HTML bruto da URL, com curl por exemplo, sem executar JavaScript, e compare com o que aparece no navegador. Se o conteúdo principal só aparece depois da execução do script, e não está no HTML bruto, essa rota está dependendo de renderização no cliente para esse conteúdo.
O componente next/image já cuida de tudo relacionado a Core Web Vitals?
Cuida de boa parte, especialmente do deslocamento de layout causado por imagens sem dimensão definida. Não cuida sozinho da prioridade de carregamento da imagem de LCP — isso precisa ser marcado explicitamente com a propriedade priority — nem de fatores de performance que vêm de fora da imagem em si, como o tempo de resposta do servidor.