Em 2023 lancei um site de e-commerce multilíngue para um cliente baseado em Berlim. Doze idiomas, App Router, tudo parecia organizado. Três meses depois suas páginas de produtos em alemão estavam classificando nos SERPs do Reino Unido e suas páginas em inglês estavam sendo servidas para usuários em Munique. As tags hreflang estavam tecnicamente presentes. Simplesmente estavam erradas de um jeito que levou uma tarde embaraçosa para eu desvendar. Essa experiência me ensinou mais sobre Next.js i18n do que qualquer página de documentação jamais fez.
Este não é um post "aqui está os documentos oficiais resumidos". É o que aprendi construindo sites multilíngues na Seahawk Media, o que afeta as pessoas repetidamente, e as decisões específicas que realmente impactam rankings em 2026.
---
O App Router Mudou Tudo (E Nem Tudo Para Melhor)
O Pages Router tinha suporte i18n integrado. Você colocava um bloco em next.config.js e Next.js tratava detecção de locale, roteamento sub-path e comutação de locale do componente Link quase automaticamente. Não era perfeito, mas era opinativo, o que significava menos armadilhas.next.config.js and Next.js handled locale detection, sub-path routing, and Link component locale switching almost automatically. It wasn't perfect, but it was opinionated, which meant fewer footguns.
O App Router removeu completamente o roteamento i18n nativo. Sem chave i18n no next.config.js. Sem detecção automática de locale. Agora você é responsável pela lógica de roteamento completa, e essa responsabilidade é onde a maioria dos times silenciosamente faz uma bagunça.i18n key in next.config.js. No automatic locale detection. You are now responsible for the full routing logic, and that responsibility is where most teams quietly make a mess.
A abordagem abençoada em 2026 é um segmento dinâmico [locale] na raiz do seu diretório app/. Algo como app/[locale]/page.tsx. Combine isso com next-intl ou i18next via react-i18next, um arquivo middleware que lê headers Accept-Language e redireciona acordamente, e você tem uma base funcionando.[locale] dynamic segment at the root of your app/ directory. Something like app/[locale]/page.tsx . Pair that with next-intl or i18next via react-i18next, a middleware file that reads Accept-Language headers and redirects accordingly, and you have a working foundation.
Por Que Middleware É Mal-Entendido
O arquivo middleware está fazendo dois trabalhos separados que as pessoas confundem. Primeiro, detecção de locale: ler o header Accept-Language (ou um cookie, ou um prefixo de URL) e decidir qual locale servir. Segundo, redirecionar ou reescrever a URL para que o usuário acabe em /de/produkte em vez de /produkte com algum estado de locale escondido.Accept-Language header (or a cookie, or a URL prefix) and deciding which locale to serve. Second, redirecting or rewriting the URL so the user ends up at /de/produkte rather than /produkte with some hidden locale state.
Ambos precisam acontecer antes da página renderizar. Isso soa óbvio. Mas vi times executarem detecção de locale dentro de um provedor de contexto React, o que significa que o HTML inicial servido ao Googlebot não tem sinal de locale algum. O bot vê uma página, a indexa sob a URL raiz, e de repente você tem uma bagunça canônica.before the page renders. That sounds obvious. But I've seen teams run locale detection inside a React context provider, which means the initial HTML served to Googlebot has no locale signal at all. The bot sees a page, it indexes it under the root URL, and suddenly you have a canonical mess.
Mantenha a resolução de locale no middleware. Sempre.
---
Sub-path vs Sub-domínio: Escolha Um e Comprometa-se
O debate eterno. /en/about vs en.yoursite.com/about. Para a maioria dos projetos recomendo roteamento por sub-path, e eis por quê: ele concentra sua autoridade de domínio. Todo link apontando para seu .com beneficia todos os locales. Com sub-domínios você está efetivamente rodando sites separados aos olhos do Google, o que significa que você precisa construir autoridade para cada um independentemente./en/about vs en.yoursite.com/about . For most projects I recommend sub-path routing, and here's why: it concentrates your domain authority. Every link pointing at your .com benefits all locales. With sub-domains you're effectively running separate sites in Google's eyes, which means you need to build authority for each one independently.
Há uma exceção. Se você está em um mercado onde a TLD local importa enormemente para confiança (.de na Alemanha, .fr na França), uma TLD de país bate tanto sub-paths quanto sub-domínios para credibilidade local percebida. Isso não é uma afirmação de SEO, é uma afirmação de conversão. Vi testes A/B onde .de vs yourbrand.com/de moveu métricas de confiança significativamente mesmo quando os rankings eram idênticos..de in Germany, .fr in France), a country-code TLD beats both sub-paths and sub-domains for perceived local credibility. That's not an SEO claim, it's a conversion claim. I've seen A/B tests where .de vs yourbrand.com/de moved trust metrics meaningfully even when rankings were identical.
Mas você não consegue executar uma configuração de ccTLD dentro de um único aplicativo Next.js facilmente. Então para 99% dos projetos: sub-paths, um app, um domínio.
---
hreflang: A Tag Que Mente Para Você
É aqui que vejo o maior dano. hreflang não é difícil conceitualmente. Ela diz aos mecanismos de busca: "esta página tem equivalentes em outros idiomas, aqui estão." Na prática, a implementação tem três modos de falha específicos que continuo encontrando.
Modo de Falha 1: As Tags Não São Recíprocas
Toda página em seu conjunto hreflang deve fazer referência a todas as outras páginas, incluindo a si mesma. Se /en/about tem um hreflang apontando para /de/about mas /de/about não tem um hreflang apontando de volta para /en/about, Google ignora o conjunto inteiro. Não penaliza. Ignora. Como se as tags não existissem./en/about has an hreflang pointing to /de/about but /de/about does not have an hreflang pointing back to /en/about, Google ignores the whole set. Not penalises. Ignores. As if the tags don't exist.
Seahawk teve um projeto fintech no ano passado onde 40% de suas tags hreflang eram não-recíprocas porque dois desenvolvedores as implementaram independentemente em componentes diferentes sem coordenar. A correção levou uma hora. A recuperação de rankings levou dois meses.
Modo de Falha 2: A Tag x-default Está Faltando ou Mal Posicionada
x-default é para páginas que não direcionam um idioma ou região específicos. Tipicamente sua raiz / ou uma página de seletor de idioma. Alguns times colocam x-default em sua página em inglês, o que é aceitável se inglês é genuinamente seu fallback para todos os locales não correspondidos. Mas se você serve uma página diferente para usuários cujo idioma você não suporta, x-default deve apontar para lá. is for pages that don't target a specific language or region. Typically your root / or a language-selector page. Some teams put x-default on their English page, which is fine if English is genuinely your fallback for all unmatched locales. But if you serve a different page to users whose language you don't support, x-default should point there.
Já vi x-default omitido inteiramente. Google não lança um erro. Simplesmente toma sua própria decisão sobre qual versão mostrar para consultas não correspondidas, e essa decisão muitas vezes é errada.x-default omitted entirely. Google doesn't throw an error. It just makes its own decision about which version to show for unmatched queries, and that decision is often wrong.
Modo de Falha 3: Barras Finais e Conflitos de Canonical
Sua tag hreflang diz https://yoursite.com/de/ueber-uns . Sua tag canonical na mesma página diz https://yoursite.com/de/ueber-uns/ (note a barra final). Google trata essas como URLs diferentes. Seu canonical e hreflang agora estão apontando para coisas diferentes, e você criou um conflito de sinal que suprime silenciosamente rankings sem nenhum erro visível no Search Console.https://yoursite.com/de/ueber-uns . Your canonical tag on the same page says https://yoursite.com/de/ueber-uns/ (note the trailing slash). Google treats those as different URLs. Your canonical and hreflang are now pointing at different things, and you've created a signal conflict that quietly suppresses rankings without any visible error in Search Console.
Configure trailingSlash em next.config.js e seja consistente em todos os lugares. Verifique. Realmente verifique fazendo curl nas suas próprias URLs e lendo o output.trailingSlash in next.config.js and be consistent everywhere. Check it. Actually check it by curling your own URLs and reading the output.
A documentação do Google Search Central sobre hreflang realmente vale a pena ler na íntegra. É um daqueles raros documentos oficiais que é específico o bastante para ser útil.Google Search Central documentation on hreflang is genuinely worth reading in full. It's one of those rare official docs that's specific enough to be useful.
---
Gerando hreflang em Escala no Next.js
Para um site com 5 idiomas você consegue escrever tags hreflang à mão. Para 12 locais em 3.000 páginas de produtos, você precisa de uma abordagem sistemática.
Aqui está o que eu uso:
- Defina sua lista de locais uma única vez, em um único arquivo de config. Algo como locales.config.ts exportando um array: ['en', 'de', 'fr', 'es', 'nl'...].
locales.config.tsexporting an array:['en', 'de', 'fr', 'es', 'nl'...]. - Construa uma função utilitária getHreflangAlternates(pathname: string) que toma um caminho agnóstico de locale (/about ) e retorna um array de objetos { hreflang, href } para cada locale.
getHreflangAlternates(pathname: string)that takes a locale-agnostic path (/about) and returns an array of{ hreflang, href }objects for every locale. - Chame essa função no seu root layout.tsx e injete os resultados via a API metadata do Next.js usando o campo alternates.languages. Isso foi adicionado no Next.js 13.3 e gera automaticamente as tags <link rel="alternate" hreflang="..."> corretas no <head>.
layout.tsxand inject the results via Next.js'smetadataAPI using thealternates.languagesfield. This was added in Next.js 13.3 and it generates the correct<link rel="alternate" hreflang="...">tags in<head>automatically. - Para páginas com conteúdo dinâmico (posts de blog, páginas de produtos) onde nem toda locale tem tradução, passe um array supportedLocales para seu utilitário e gere tags apenas para locales onde o conteúdo realmente existe. Uma tag hreflang apontando para uma página que retorna 404 é pior do que nenhuma tag hreflang.
supportedLocalesarray to your utility and only generate tags for locales where the content actually exists. A hreflang tag pointing to a page that returns 404 is worse than no hreflang tag.
A abordagem alternates.languages na API metadata é a forma mais limpa que encontrei para lidar com isso. Sem malabarismo com <Head> customizado, sem pacotes de terceiros para as tags em si.alternates.languages approach in the metadata API is the cleanest way I've found to handle this. No custom <Head> juggling, no third-party packages for the tags themselves.
---
Detecção de Locale Sem Ser Chato
Há uma armadilha de UX aqui que também tem implicações de SEO. Redirecionamento automático agressivo baseado na locale do navegador é ruim para ambos.
Se um usuário na Alemanha visita yoursite.com/en/article e seu middleware faz um hard-redirect para /de/artikel sem perguntar, você quebrou a navegação pretendida dele. Você também potencialmente quebrou o compartilhamento de links: alguém compartilha a URL em inglês, o destinatário na França é redirecionado para francês, e o conteúdo que deveria ser visto pode não existir em francês ainda.yoursite.com/en/article and your middleware hard-redirects them to /de/artikel without asking, you've just broken their intended navigation. You've also potentially broken link sharing: someone shares the English URL, the recipient in France gets bounced to French, and the content they were meant to see might not exist in French yet.
Minha regra: detecte, sugira, não force. Use o header Accept-Language para definir um padrão sensato na primeira visita (armazenado em um cookie), mas deixe usuários o sobrescreverem manualmente e respeite esse override em toda visita subsequente. O cookie tem prioridade sobre o header.detect, suggest, don't force. Use the Accept-Language header to set a sensible default on first visit (stored in a cookie), but let users override it manually and honour that override on every subsequent visit. The cookie takes priority over the header.
Para SEO especificamente: Googlebot não envia headers Accept-Language significativos. Ele fará crawl das suas URLs como estão. Então certifique-se de que toda URL de locale é diretamente acessível sem redirecionamento, e que seu sitemap inclui explicitamente todas as variantes de locale.Accept-Language headers. It will crawl your URLs as-is. So make sure every locale URL is directly accessible without redirection, and that your sitemap includes all locale variants explicitly.
A documentação do next-intl middleware cobre bem essa estratégia de detecção, e é para onde eu encaminharia qualquer um começando um novo projeto.next-intl middleware documentation covers this detection strategy well, and it's where I'd send anyone starting a new project.
---
Sitemaps para Sites Multilíngues
Um único sitemap.xml que inclui todas as variantes de localidade funciona bem para sites menores. Para sites grandes (dezenas de milhares de URLs em múltiplas localidades) eu divido: um índice de sitemap em /sitemap.xml, depois arquivos de sitemap separados por localidade em /sitemaps/en.xml, /sitemaps/de.xml, e assim por diante.sitemap.xml that includes every locale variant is fine for smaller sites. For large sites (tens of thousands of URLs across multiple locales) I split it: one sitemap index at /sitemap.xml , then separate sitemap files per locale at /sitemaps/en.xml , /sitemaps/de.xml, and so on.
Cada entrada de URL deve incluir tags <xhtml:link rel="alternate"> que espelhem sua configuração de hreflang. Isso é redundante com as tags na página, mas ajuda o Googlebot a conectar os pontos em páginas que ele ainda não rastreou.<xhtml:link rel="alternate"> tags mirroring your hreflang setup. This is redundant with the in-page tags but it helps Googlebot connect the dots on pages it hasn't crawled yet.
No Next.js 14+ você pode gerar sitemaps dinamicamente via app/sitemap.ts. O tipo de retorno suporta alternates.languages no mesmo formato da API de metadata. Conecte-a ao mesmo utilitário getHreflangAlternates que você construiu antes e você consegue consistência de graça.app/sitemap.ts . The return type supports alternates.languages in the same shape as the metadata API. Wire it up to the same getHreflangAlternates utility you built earlier and you get consistency for free.
Um número concreto: em um projeto com 8 localidades e 4.500 produtos, dividir sitemaps reduziu o tempo médio de rastreamento por localidade em aproximadamente 30% com base nas estatísticas de rastreamento do GSC. Não é sensacional, mas é significativo.
---
Bibliotecas de Tradução: O Que Realmente Uso
Há três que vale a pena conhecer. next-intl é minha padrão para projetos com App Router. Tem suporte de primeira classe para server components, lida com pluralização corretamente, e a API é limpa. Usei em cerca de 40 projetos desde meados de 2023.next-intl is my default for App Router projects. It has first-class support for server components, handles pluralisation properly, and the API is clean. I've used it on about 40 projects since mid-2023.
react-i18next é testado em batalha e tem um ecossistema enorme, mas seu modelo mental foi construído para client components e adicionar suporte a server components via o núcleo do i18next parece remendado comparado à abordagem nativa do next-intl.i18next core feels bolted-on compared to next-intl's native approach.
Lingui vale a pena se seu fluxo de tradução envolve tradutores profissionais em vez de um CMS. Suas ferramentas de extração são superiores. Mas para a maioria dos projetos de agência onde o conteúdo vem do Contentful ou Sanity, next-intl é mais simples.
O que eu evitaria: rolar a sua própria solução. Fiz isso uma vez em 2020 para um projeto onde o cliente insistiu que seus arquivos de tradução PHP existentes fossem reutilizados diretamente. Funcionou, mal e bem, e passei dois anos mantendo algo que next-intl agora resolve com uma única chamada useTranslations().useTranslations() call.
---
FAQ
O Next.js App Router suporta o antigo bloco `next.config.js` i18n?
Não. A chave de configuração i18n era específica do Pages Router. Se você está migrando para App Router, precisa implementar roteamento via um segmento dinâmico [locale] e um arquivo middleware. O bloco config antigo é silenciosamente ignorado em projetos App Router.i18n configuration key was specific to the Pages Router. If you're migrating to App Router, you need to implement routing via a [locale] dynamic segment and a middleware file. The old config block is silently ignored in App Router projects.
Devo usar `lang` no caminho da URL ou confiar em subdomínios?
Para a maioria dos projetos, lang no caminho da URL (sub-path routing como /de/) é a decisão certa. Subdomínios fragmentam sua autoridade de domínio e exigem configuração DNS separada. ccTLDs valem a complexidade apenas se sinais de confiança local estão genuinamente movimentando as taxas de conversão em seus mercados específicos.lang in the URL path (sub-path routing like /de/) is the right call. Subdomains fragment your domain authority and require separate DNS configuration. ccTLDs are only worth the complexity if local trust signals are genuinely moving conversion rates in your specific markets.
Como o Googlebot lida com conteúdo de locale renderizado por JavaScript?
Mal, às vezes. Se sua detecção de locale é executada puramente no lado do cliente em um contexto React e o HTML inicial não tem marcador de locale, o Googlebot pode indexar o estado pré-hidratação. Sempre resolva locale no middleware e garanta que o atributo lang correto esteja no elemento <html> no HTML renderizado pelo servidor. Verifique isso visualizando o código-fonte, não usando devtools do navegador (que mostram o estado pós-hidratação).lang attribute is on the <html> element in the server-rendered HTML. Check this by viewing source, not by using browser devtools (which show post-hydration state).
Qual é o valor correto de `x-default` hreflang para um site sem página de seletor de idioma?
Aponte para a homepage da sua localidade primária, normalmente /en/. Isso sinaliza ao Google que inglês é seu fallback para usuários cujo idioma você não suporta. Não é uma penalidade fazer isso, é apenas um sinal de preferência leve que Google pode ou não honrar./en/. It signals to Google that English is your fallback for users whose language you don't support. It's not a penalty to do this, it's just a soft preference signal Google may or may not honour.
Posso verificar minha implementação de hreflang sem uma ferramenta SEO paga?
Sim. A ferramenta de Inspeção de URL do Google Search Console mostrará as tags hreflang detectadas para qualquer URL indexada. Não valida reciprocidade automaticamente, mas você pode verificar manualmente algumas páginas representativas. Para validação em massa, Screaming Frog (gratuito até 500 URLs) executa verificações de reciprocidade na sua aba de hreflang.Google Search Console's URL Inspection tool will show you detected hreflang tags for any indexed URL. It won't validate reciprocity automatically, but you can manually check a few representative pages. For bulk validation, Screaming Frog (free up to 500 URLs) handles reciprocity checks in its hreflang tab.
---
Acertar multilíngue em Next.js é genuinamente um trabalho pouco glamouroso. É arquivos de configuração, consistência de URL e garantir que dois desenvolvedores não tenham implementado a mesma coisa duas vezes em componentes diferentes. Mas os sites onde dediquei tempo para fazer direito têm rankings que se sustentam entre regiões, e os onde cortei caminho me ensinaram as lições que escrevi aqui.
O cliente de Berlin, aliás, eventualmente conseguiu suas páginas em alemão rankando na Alemanha. Levou uma recrawl completa e muito café.
