Guia completo de SEO multilíngue no Next.js: faça os buscadores indexarem cada idioma corretamente

Você caprichou em um site multilíngue, mas o buscador insiste em mostrar a versão errada. A pessoa pesquisa conteúdo em chinês e, ao clicar, cai em uma página em inglês. Enquanto isso, versões em idiomas diferentes disputam espaço nos resultados e prejudicam o posicionamento geral.
Esses são problemas típicos de uma configuração inadequada de SEO multilíngue. Segundo dados do Google, mais de 60% dos sites multilíngues têm erros na configuração de hreflang, o que compromete a internacionalização e a experiência do usuário.
Neste artigo, você verá em detalhes como implementar corretamente o SEO multilíngue no Next.js, incluindo:
- Configuração correta das tags hreflang — para evitar confusão entre versões de idioma
- Estratégias para gerar um Sitemap multilíngue — para acelerar a indexação
- Boas práticas de estrutura de URL — para escolher a melhor abordagem de internacionalização
- Diagnóstico e correção de erros comuns — para localizar e resolver problemas com rapidez
Há soluções tanto para quem usa Pages Router quanto para quem trabalha com App Router.
1. Entendendo os conceitos centrais do SEO multilíngue
1.1 O que é hreflang
hreflang é um atributo HTML usado para informar aos buscadores o idioma e a região de destino de uma página. Suas principais funções são:
- Evitar problemas de conteúdo duplicado — informa que versões em idiomas diferentes são traduções do mesmo conteúdo, e não duplicatas
- Combinar a página com o usuário certo — mostra a versão mais adequada conforme idioma e região
- Melhorar a experiência do usuário — evita que alguém encontre conteúdo no idioma errado
1.2 Como o Google trata conteúdo multilíngue
Quando o rastreador do Google acessa um site multilíngue, ele segue estas etapas:
- Detecta o idioma da página por meio do atributo HTML
lang, das tags hreflang e da análise do conteúdo - Procura as tags hreflang para entender a relação entre as páginas em idiomas diferentes
- Exibe nos resultados a versão correspondente à preferência de idioma do usuário
- Consolida os sinais de SEO das versões em idiomas diferentes, em vez de fazê-las competir entre si
1.3 Exemplos de erros comuns de SEO
Erro 1: tags hreflang ausentes
<!-- ❌ Incorreto: não há tags hreflang -->
<head>
<title>My Website</title>
<link rel="canonical" href="https://example.com/en/about" />
</head>
Consequência: o buscador não identifica a relação entre os idiomas e pode mostrar a versão errada nos resultados.
Erro 2: configuração assimétrica de hreflang
<!-- Página em inglês -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<!-- ❌ Página em chinês — incorreto: as tags hreflang estão ausentes -->
<!-- Cada versão deve conter a configuração hreflang completa -->
Consequência: o Google exige reciprocidade entre as tags hreflang e ignora configurações unilaterais.
Erro 3: código de idioma incorreto
<!-- ❌ Incorreto: códigos de idioma fora do padrão -->
<link rel="alternate" hreflang="cn" href="..." /> <!-- deveria ser zh -->
<link rel="alternate" hreflang="en-us" href="..." /> <!-- deveria ser en-US; observe as maiúsculas -->
Consequência: o buscador não reconhece o código e a configuração hreflang deixa de funcionar.
2. Escolhendo uma estratégia de URL
Antes de implementar um site multilíngue, escolha a estratégia de URL. Essa decisão afeta o SEO, a experiência do usuário e toda a implementação técnica.
2.1 Comparação das três principais estratégias
| Estratégia | Exemplo | Impacto no SEO | Dificuldade | Recomendação |
|---|---|---|---|---|
| Subdiretório | example.com/en/ example.com/zh/ | ⭐⭐⭐⭐⭐ Excelente | ⭐⭐⭐ Média | ⭐⭐⭐⭐⭐ |
| Subdomínio | en.example.com zh.example.com | ⭐⭐⭐ Razoável | ⭐⭐⭐⭐ Alta | ⭐⭐⭐ |
| Parâmetro de URL | example.com?lang=en | ⭐⭐ Fraco | ⭐⭐⭐⭐⭐ Baixa | ⭐⭐ |
2.2 Análise detalhada de cada estratégia
Opção 1: subdiretórios (recomendada)
Vantagens:
- A autoridade de SEO fica concentrada no domínio principal, favorecendo o posicionamento geral
- A configuração é simples, sem domínios e certificados SSL adicionais
- A manutenção e a expansão são fáceis, com uma implantação única do código
- O Next.js oferece suporte nativo, simplificando a implementação
Desvantagem:
- Todos os idiomas compartilham o mesmo domínio, o que impede otimizações de DNS específicas para cada mercado
Implementação no Next.js:
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'zh', 'ja', 'de'],
defaultLocale: 'en',
localeDetection: true // detecta automaticamente o idioma do usuário
}
}
Opção 2: subdomínios
Vantagens:
- Cada mercado pode ser implantado em um servidor diferente, como uma infraestrutura dedicada para a China
- As tecnologias podem ser independentes, aumentando a flexibilidade
- Facilita a otimização de CDN e localização geográfica
Desvantagens:
- A autoridade de SEO fica dividida e cada subdomínio precisa desenvolver sua própria relevância
- Exige gestão adicional de domínios e certificados SSL
- O custo de implementação e manutenção é mais alto
Opção 3: parâmetros de URL (não recomendada)
Vantagem:
- É a opção mais simples de implementar
Desvantagens:
- Tem o pior desempenho de SEO, pois os buscadores podem ignorar o parâmetro
- A experiência é pior e as URLs são menos amigáveis
- Dificulta a otimização de cache em CDN
- Não permite diferenciar claramente os idiomas nos resultados de busca
Conclusão:
Para a maioria dos projetos, a estratégia por subdiretórios é a mais recomendada. Ela oferece o melhor equilíbrio entre SEO, dificuldade de implementação e custo de manutenção.
3. Configuração detalhada de hreflang
3.1 Para que servem as tags hreflang
As tags hreflang informam aos buscadores:
- Quais versões de idioma uma página possui
- Qual é a URL completa de cada versão
- A qual idioma e região cada versão se destina
3.2 Configurando hreflang no App Router do Next.js
Método 1: usar a Metadata API (recomendado)
O App Router do Next.js 13+ oferece uma Metadata API mais simples:
// app/[lang]/about/page.tsx
import { Metadata } from 'next'
type Props = {
params: { lang: string }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang } = params
// Define os idiomas aceitos
const languages = ['en', 'zh', 'ja', 'de']
// Gera os links alternativos de todos os idiomas
const alternates = {
canonical: `https://example.com/${lang}/about`,
languages: languages.reduce((acc, locale) => {
acc[locale] = `https://example.com/${locale}/about`
return acc
}, {} as Record<string, string>)
}
return {
title: 'About Us',
alternates,
// Adiciona x-default para idiomas sem correspondência
other: {
'x-default': 'https://example.com/en/about'
}
}
}
export default function AboutPage({ params }: Props) {
return <div>About page in {params.lang}</div>
}
Método 2: usar um componente Head personalizado
Essa opção é adequada quando você precisa de mais controle:
// components/I18nHead.tsx
import Head from 'next/head'
interface I18nHeadProps {
currentLang: string
pathname: string
languages?: string[]
}
export default function I18nHead({
currentLang,
pathname,
languages = ['en', 'zh', 'ja', 'de']
}: I18nHeadProps) {
const baseUrl = 'https://example.com'
return (
<Head>
{/* URL canonical da página atual */}
<link rel="canonical" href={`${baseUrl}/${currentLang}${pathname}`} />
{/* Tags hreflang de todas as versões */}
{languages.map(lang => (
<link
key={lang}
rel="alternate"
hrefLang={lang}
href={`${baseUrl}/${lang}${pathname}`}
/>
))}
{/* x-default aponta para o idioma padrão */}
<link
rel="alternate"
hrefLang="x-default"
href={`${baseUrl}/en${pathname}`}
/>
</Head>
)
}
Uso:
// app/[lang]/about/page.tsx
import I18nHead from '@/components/I18nHead'
export default function AboutPage({ params }: { params: { lang: string } }) {
return (
<>
<I18nHead
currentLang={params.lang}
pathname="/about"
/>
<div>About page content</div>
</>
)
}
3.3 Configurando hreflang no Pages Router do Next.js
O Pages Router usa APIs diferentes:
// pages/about.tsx
import { GetStaticProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
export default function AboutPage() {
const router = useRouter()
const { locale, locales, asPath } = router
const baseUrl = 'https://example.com'
return (
<>
<Head>
{/* URL canonical da página atual */}
<link rel="canonical" href={`${baseUrl}/${locale}${asPath}`} />
{/* Tags hreflang de todas as versões */}
{locales?.map(loc => (
<link
key={loc}
rel="alternate"
hrefLang={loc}
href={`${baseUrl}/${loc}${asPath}`}
/>
))}
{/* x-default: idioma padrão */}
<link
rel="alternate"
hrefLang="x-default"
href={`${baseUrl}/en${asPath}`}
/>
</Head>
<div>About page content</div>
</>
)
}
export const getStaticProps: GetStaticProps = async ({ locale }) => {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default
}
}
}
3.4 Configuração avançada com códigos de região
Se o site oferece conteúdo específico para determinados países ou regiões, use o formato language-REGION:
// Usuários de inglês em regiões diferentes
const hreflangConfig = {
'en-US': 'https://example.com/en-us/about', // inglês dos Estados Unidos
'en-GB': 'https://example.com/en-gb/about', // inglês do Reino Unido
'en-AU': 'https://example.com/en-au/about', // inglês da Austrália
'zh-CN': 'https://example.com/zh-cn/about', // chinês simplificado da China continental
'zh-TW': 'https://example.com/zh-tw/about', // chinês tradicional de Taiwan
'zh-HK': 'https://example.com/zh-hk/about', // chinês tradicional de Hong Kong
}
Rotas por região no Next.js:
// next.config.js
module.exports = {
i18n: {
locales: ['en-US', 'en-GB', 'en-AU', 'zh-CN', 'zh-TW', 'zh-HK'],
defaultLocale: 'en-US',
}
}
3.5 Erros comuns de configuração e como corrigir
Erro 1: falta de autorreferência
<!-- ❌ Incorreto: a página atual não aponta para si mesma -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<!-- ✅ Correto: inclua uma referência à própria página -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
Por que a autorreferência é obrigatória?
O Google exige uma configuração simétrica: cada versão deve apontar para todas as outras, inclusive para si mesma.
Erro 2: x-default ausente
<!-- ✅ Recomendado: adicione x-default como idioma padrão -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
x-default fornece uma versão padrão quando nenhum idioma corresponde ao usuário. Por exemplo:
- O navegador do usuário está em árabe, mas o site não oferece árabe
- O buscador retorna a página indicada por
x-default
Erro 3: conflito entre hreflang e canonical
<!-- ❌ Incorreto: canonical aponta para outro idioma -->
<link rel="canonical" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<!-- ✅ Correto: canonical aponta para o idioma da página atual -->
<link rel="canonical" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
Princípio central: a tag canonical deve apontar para a URL da própria página, nunca para uma versão em outro idioma.
4. Implementando um Sitemap multilíngue
O Sitemap ajuda os buscadores a descobrir e indexar suas páginas. Em um site multilíngue, uma configuração correta é especialmente importante.
4.1 Por que usar um Sitemap multilíngue
Ele oferece três benefícios principais:
- Indexação mais rápida — informa ativamente todas as versões, sem depender de o rastreador encontrá-las sozinho
- Cobertura completa — reduz o risco de omitir idiomas, sobretudo em páginas profundas
- Sinais de hreflang — também permite declarar as relações de idioma no Sitemap
4.2 Escolhendo uma estratégia de Sitemap
Escolha a abordagem conforme o tamanho do site:
Opção 1: um único Sitemap (recomendada para sites pequenos)
Coloque as URLs de todos os idiomas em um único sitemap.xml. É adequada para sites com menos de 5.000 páginas:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<!-- Versão em inglês -->
<url>
<loc>https://example.com/en/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
<xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about"/>
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
</url>
<!-- Versão em chinês -->
<url>
<loc>https://example.com/zh/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
<xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about"/>
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
</url>
</urlset>
Opção 2: um Sitemap por idioma (recomendada para sites grandes)
Crie um Sitemap por idioma e reúna todos em um índice. Essa abordagem é indicada quando há mais de 5.000 páginas ou muitos idiomas:
<!-- sitemap-index.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-en.xml</loc>
<lastmod>2025-01-01</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-zh.xml</loc>
<lastmod>2025-01-01</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-ja.xml</loc>
<lastmod>2025-01-01</lastmod>
</sitemap>
</sitemapindex>
4.3 Gerando um Sitemap no App Router
O Next.js 13+ inclui uma API prática para essa tarefa:
// app/sitemap.ts
import { MetadataRoute } from 'next'
// Idiomas aceitos
const languages = ['en', 'zh', 'ja', 'de']
// Rotas sem o prefixo de idioma
const routes = ['', '/about', '/blog', '/contact']
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://example.com'
const sitemap: MetadataRoute.Sitemap = []
// Gera todas as versões de idioma para cada rota
routes.forEach(route => {
languages.forEach(lang => {
const url = `${baseUrl}/${lang}${route}`
sitemap.push({
url,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: route === '' ? 1 : 0.8,
// O Next.js processa alternateRefs automaticamente
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}${route}`
return acc
}, {} as Record<string, string>)
}
})
})
})
return sitemap
}
4.4 Sitemap para conteúdo dinâmico
Se o site tem conteúdo dinâmico, como artigos ou produtos, obtenha os dados do banco ou do CMS:
// app/sitemap.ts
import { MetadataRoute } from 'next'
const languages = ['en', 'zh', 'ja']
const baseUrl = 'https://example.com'
// Obtém os artigos do banco ou CMS
async function getArticles() {
// Em um projeto real, busque os dados no banco ou pela API do CMS
// Exemplo: const articles = await prisma.article.findMany()
return [
{ slug: 'getting-started', lastModified: '2025-01-01' },
{ slug: 'advanced-guide', lastModified: '2025-01-15' },
]
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const sitemap: MetadataRoute.Sitemap = []
// 1. Adiciona páginas estáticas
const staticPages = ['', '/about', '/contact']
staticPages.forEach(page => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}${page}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: page === '' ? 1 : 0.8,
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}${page}`
return acc
}, {} as Record<string, string>)
}
})
})
})
// 2. Adiciona conteúdo dinâmico, como artigos
const articles = await getArticles()
articles.forEach(article => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}/blog/${article.slug}`,
lastModified: new Date(article.lastModified),
changeFrequency: 'weekly',
priority: 0.6,
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
return acc
}, {} as Record<string, string>)
}
})
})
})
return sitemap
}
4.5 Gerando um Sitemap no Pages Router
No Pages Router, crie manualmente uma rota de API:
// pages/api/sitemap.xml.ts
import { NextApiRequest, NextApiResponse } from 'next'
const baseUrl = 'https://example.com'
const languages = ['en', 'zh', 'ja']
function generateSiteMap(pages: string[]) {
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
${pages.map(page => {
return languages.map(lang => {
const url = `${baseUrl}/${lang}${page}`
const alternates = languages.map(l =>
` <xhtml:link rel="alternate" hreflang="${l}" href="${baseUrl}/${l}${page}"/>`
).join('\n')
return ` <url>
<loc>${url}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
${alternates}
<xhtml:link rel="alternate" hreflang="x-default" href="${baseUrl}/en${page}"/>
</url>`
}).join('\n')
}).join('\n')}
</urlset>`
}
export default function handler(req: NextApiRequest, res: NextApiResponse) {
// Define todas as rotas do site
const pages = ['', '/about', '/blog', '/contact']
const sitemap = generateSiteMap(pages)
res.setHeader('Content-Type', 'text/xml')
res.write(sitemap)
res.end()
}
4.6 Enviando o Sitemap aos buscadores
Depois de gerar o Sitemap, envie-o aos buscadores para acelerar a indexação.
Método 1: declarar no robots.txt
É o método mais simples; os rastreadores leem a declaração automaticamente:
# public/robots.txt
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Método 2: enviar ao Google Search Console
O envio manual pode iniciar a descoberta imediatamente:
- Acesse o Google Search Console
- Selecione a propriedade do site
- No menu lateral, escolha “Sitemaps”
- Informe a URL do
sitemap.xml - Clique em “Enviar”
Método 3: enviar ao Bing Webmaster Tools
O Bing também tem participação relevante no mercado:
- Acesse o Bing Webmaster Tools
- Adicione o site
- Envie o
sitemap.xmlna seção de Sitemaps
4.7 Validando o Sitemap
Use estas ferramentas para conferir o formato:
- XML Sitemap Validator: https://www.xml-sitemaps.com/validate-xml-sitemap.html
- Google Search Console: consulte o status da indexação e os erros após o envio
- Validadores XML online: confirme que o documento segue o padrão XML
5. Boas práticas e cuidados
5.1 A importância da qualidade da tradução
Os buscadores, especialmente o Google, conseguem identificar traduções de baixa qualidade, o que afeta diretamente o posicionamento.
Evite:
- ❌ Publicar diretamente conteúdo gerado pelo Google Translate ou por outras ferramentas automáticas
- ❌ Traduzir apenas a navegação e o título, deixando o corpo em outro idioma
- ❌ Manter diferenças grandes de estrutura e quantidade de informação entre idiomas
Faça:
- ✅ Contrate profissionais ou falantes nativos para revisar a tradução
- ✅ Localize o conteúdo, levando em conta diferenças culturais e hábitos de expressão
- ✅ Preserve a consistência de conteúdo e de qualidade entre os idiomas
5.2 Evitando riscos de SEO da tradução automática
A tradução automática no cliente não ajuda o SEO, pois o rastreador vê apenas o conteúdo original:
// ❌ Não recomendado: tradução automática no cliente, que não pode ser indexada
import GoogleTranslate from 'google-translate-api'
export default function Page() {
const [content, setContent] = useState('')
useEffect(() => {
// Esta abordagem não contribui para o SEO
GoogleTranslate(originalText, { to: 'zh' })
.then(res => setContent(res.text))
}, [])
return <div>{content}</div>
}
// ✅ Recomendado: renderizar no servidor o conteúdo realmente traduzido
export default function Page({ params }: { params: { lang: string } }) {
// Obtém a tradução real do banco de dados ou do sistema de arquivos
const content = await getTranslatedContent(params.lang)
return <div>{content}</div>
}
5.3 Recomendações de desempenho
Sites multilíngues normalmente atendem pessoas no mundo todo, portanto o desempenho é ainda mais importante.
1. Use uma CDN para acelerar o acesso em várias regiões
// next.config.js
module.exports = {
images: {
domains: ['cdn.example.com'],
},
// Ativa a compactação automática
compress: true,
}
2. Carregue os pacotes de idioma sob demanda
Evite carregar os arquivos de todos os idiomas de uma vez:
// Importa dinamicamente o arquivo do idioma atual
const messages = await import(`@/locales/${lang}.json`)
3. Estratégia de cache
Defina um tempo de cache adequado para as páginas:
// app/[lang]/layout.tsx
export const revalidate = 3600 // revalida a cada hora
5.4 Monitoramento e manutenção
SEO multilíngue não é uma tarefa pontual; exige monitoramento e melhoria contínuos.
1. Verifique regularmente os erros de hreflang
Use o relatório de segmentação internacional do Google Search Console:
- Confira erros e avisos das tags hreflang
- Acompanhe o status de indexação de cada idioma
- Monitore desempenho e taxa de cliques por versão
2. Ferramentas recomendadas
- Google Search Console — ferramenta oficial, gratuita e essencial
- Ahrefs Site Audit — ferramenta profissional para verificar hreflang em lote
- Screaming Frog — rastreador que faz auditorias locais do site
- hreflang Tags Testing Tool — validador online: https://www.aleydasolis.com/english/international-seo-tools/hreflang-tags-generator/
3. Crie um script de monitoramento
Automatize a verificação de hreflang:
// scripts/check-hreflang.ts
import { JSDOM } from 'jsdom'
async function checkHreflang(url: string) {
const response = await fetch(url)
const html = await response.text()
const dom = new JSDOM(html)
const document = dom.window.document
const hreflangLinks = document.querySelectorAll('link[rel="alternate"][hreflang]')
console.log(`Found ${hreflangLinks.length} hreflang links on ${url}`)
hreflangLinks.forEach(link => {
const hreflang = link.getAttribute('hreflang')
const href = link.getAttribute('href')
console.log(` ${hreflang}: ${href}`)
})
// Verifica se há autorreferência
const currentUrl = new URL(url).href
const hasSelfReference = Array.from(hreflangLinks).some(
link => link.getAttribute('href') === currentUrl
)
if (!hasSelfReference) {
console.warn('⚠️ Warning: Missing self-reference hreflang tag')
}
// Verifica se há x-default
const hasXDefault = Array.from(hreflangLinks).some(
link => link.getAttribute('hreflang') === 'x-default'
)
if (!hasXDefault) {
console.warn('⚠️ Warning: Missing x-default hreflang tag')
}
}
// Exemplos de uso
checkHreflang('https://example.com/en/about')
checkHreflang('https://example.com/zh/about')
6. Exemplo prático: um projeto completo
Veja agora como implementar SEO multilíngue em um projeto completo com o App Router do Next.js.
6.1 Estrutura do projeto
my-i18n-site/
├── app/
│ ├── [lang]/ # rota dinâmica de idioma
│ │ ├── layout.tsx # layout do idioma
│ │ ├── page.tsx # página inicial
│ │ ├── about/
│ │ │ └── page.tsx # página Sobre
│ │ └── blog/
│ │ ├── page.tsx # lista de artigos
│ │ └── [slug]/
│ │ └── page.tsx # página do artigo
│ ├── sitemap.ts # gerador do Sitemap
│ └── robots.ts # gerador do robots.txt
├── components/
│ └── I18nMetadata.tsx # componente de metadados multilíngues
├── lib/
│ ├── i18n.ts # configuração de internacionalização
│ └── articles.ts # acesso aos dados dos artigos
├── locales/ # arquivos de tradução
│ ├── en.json
│ ├── zh.json
│ └── ja.json
└── next.config.js # configuração do Next.js
6.2 Arquivos de configuração
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Atenção: o App Router não usa a configuração i18n
// As rotas de idioma devem ser implementadas manualmente
}
module.exports = nextConfig
// lib/i18n.ts
export const languages = ['en', 'zh', 'ja'] as const
export type Language = (typeof languages)[number]
export const defaultLanguage: Language = 'en'
export const languageNames: Record<Language, string> = {
en: 'English',
zh: '中文',
ja: '日本語',
}
export function isValidLanguage(lang: string): lang is Language {
return languages.includes(lang as Language)
}
6.3 Componente Layout
// app/[lang]/layout.tsx
import { languages, isValidLanguage } from '@/lib/i18n'
import { notFound } from 'next/navigation'
export async function generateStaticParams() {
return languages.map(lang => ({ lang }))
}
export default function LangLayout({
children,
params,
}: {
children: React.ReactNode
params: { lang: string }
}) {
// Valida o código do idioma
if (!isValidLanguage(params.lang)) {
notFound()
}
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}
6.4 Página com Metadata
// app/[lang]/about/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'
type Props = {
params: { lang: Language }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang } = params
const baseUrl = 'https://example.com'
const pathname = '/about'
// Gera a configuração de alternates
const alternates = {
canonical: `${baseUrl}/${lang}${pathname}`,
languages: languages.reduce((acc, locale) => {
acc[locale] = `${baseUrl}/${locale}${pathname}`
return acc
}, {} as Record<string, string>)
}
// Retorna título e descrição conforme o idioma
const titles: Record<Language, string> = {
en: 'About Us - Learn More About Our Company',
zh: '关于我们 - 了解更多关于我们公司的信息',
ja: '私たちについて - 当社についてもっと知る',
}
const descriptions: Record<Language, string> = {
en: 'Learn about our mission, values, and the team behind our success.',
zh: '了解我们的使命、价值观以及我们成功背后的团队。',
ja: '私たちの使命、価値観、そして成功を支えるチームについて学びます。',
}
return {
title: titles[lang],
description: descriptions[lang],
alternates,
openGraph: {
title: titles[lang],
description: descriptions[lang],
url: `${baseUrl}/${lang}${pathname}`,
siteName: 'Example Site',
locale: lang,
type: 'website',
},
}
}
export default function AboutPage({ params }: Props) {
const content = {
en: 'About us content in English...',
zh: '关于我们的中文内容...',
ja: '私たちについての日本語コンテンツ...',
}
return (
<div>
<h1>About Us</h1>
<p>{content[params.lang]}</p>
</div>
)
}
6.5 Rota dinâmica com hreflang
// app/[lang]/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'
import { getArticle, getAllArticles } from '@/lib/articles'
import { notFound } from 'next/navigation'
type Props = {
params: { lang: Language; slug: string }
}
// Gera estaticamente todas as páginas de artigos
export async function generateStaticParams() {
const articles = await getAllArticles()
return languages.flatMap(lang =>
articles.map(article => ({
lang,
slug: article.slug,
}))
)
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang, slug } = params
const article = await getArticle(slug, lang)
if (!article) {
return {}
}
const baseUrl = 'https://example.com'
const pathname = `/blog/${slug}`
const alternates = {
canonical: `${baseUrl}/${lang}${pathname}`,
languages: languages.reduce((acc, locale) => {
acc[locale] = `${baseUrl}/${locale}${pathname}`
return acc
}, {} as Record<string, string>)
}
return {
title: article.title,
description: article.excerpt,
alternates,
openGraph: {
title: article.title,
description: article.excerpt,
url: `${baseUrl}/${lang}${pathname}`,
type: 'article',
publishedTime: article.publishedAt,
authors: [article.author],
},
}
}
export default async function BlogArticle({ params }: Props) {
const { lang, slug } = params
const article = await getArticle(slug, lang)
if (!article) {
notFound()
}
return (
<article>
<h1>{article.title}</h1>
<p>{article.content}</p>
</article>
)
}
6.6 Gerando o Sitemap
// app/sitemap.ts
import { MetadataRoute } from 'next'
import { languages } from '@/lib/i18n'
import { getAllArticles } from '@/lib/articles'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
const sitemap: MetadataRoute.Sitemap = []
// 1. Adiciona páginas estáticas
const staticPages = ['', '/about', '/contact']
staticPages.forEach(page => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}${page}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: page === '' ? 1 : 0.8,
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}${page}`
return acc
}, {} as Record<string, string>)
}
})
})
})
// 2. Adiciona conteúdo dinâmico, como artigos
const articles = await getAllArticles()
articles.forEach(article => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}/blog/${article.slug}`,
lastModified: new Date(article.updatedAt),
changeFrequency: 'weekly',
priority: 0.6,
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
return acc
}, {} as Record<string, string>)
}
})
})
})
return sitemap
}
6.7 Robots.txt
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://example.com/sitemap.xml',
}
}
7. Validação e testes
7.1 Checklist de teste local
Antes da implantação em produção, confira:
- Todas as páginas têm o atributo
langcorreto na tag<html> - Cada página contém todas as tags hreflang, incluindo todos os idiomas
- As tags hreflang incluem autorreferência
- Há uma tag
x-defaultapontando para o idioma padrão - A tag canonical aponta para a URL correta, na versão do idioma atual
- O Sitemap contém as URLs de todos os idiomas
- O robots.txt aponta corretamente para o sitemap.xml
- As versões mantêm qualidade e precisão de tradução equivalentes
7.2 Google Rich Results Test
Acesse o Google Rich Results Test e teste a página:
- Informe a URL
- Aguarde o rastreamento e a análise do Google
- Confira erros e avisos
- Verifique se as tags hreflang foram reconhecidas corretamente
7.3 Ferramentas para verificar hreflang
Algumas opções específicas:
- Aleyda Solis hreflang Generator: https://www.aleydasolis.com/english/international-seo-tools/hreflang-tags-generator/
- Merkle hreflang Checker: https://technicalseo.com/tools/hreflang/
Essas ferramentas podem:
- Verificar as tags de várias páginas em lote
- Identificar problemas de reciprocidade, como referências unilaterais
- Detectar códigos de idioma incorretos
7.4 Validação no Google Search Console
Depois da implantação em produção:
- Envie o Sitemap ao Google Search Console
- Aguarde de uma a duas semanas para a indexação inicial
- Consulte o relatório “Segmentação internacional” > “Idioma”
- Verifique erros e avisos de hreflang
- Monitore o desempenho de busca de cada versão
8. Perguntas frequentes
P1: qual é a diferença entre hreflang e canonical?
- canonical — informa a URL canônica da página e ajuda a lidar com conteúdo duplicado
- hreflang — informa as versões de idioma da página e ajuda na segmentação linguística
As duas podem ser usadas em conjunto. O canonical de cada versão deve apontar para ela mesma, enquanto hreflang aponta para todas as versões.
P2: preciso configurar hreflang em todas as páginas?
Sim. As tags precisam existir em cada versão e ser recíprocas. Se apenas a página em inglês apontar para a chinesa, mas a chinesa não apontar de volta, o Google ignorará a configuração.
P3: para qual idioma x-default deve apontar?
Em geral, aponte para o idioma padrão ou para a versão mais ampla. Algumas estratégias:
- Se o público principal fala inglês, aponte para a versão em inglês
- Em um site global, aponte para a versão internacional em inglês (
en-US) - Em um site regional, aponte para o idioma principal da região
P4: subdiretório ou subdomínio, qual é melhor?
Subdiretório (recomendado):
- Concentra a autoridade de SEO no domínio principal
- É simples de implementar e manter
- Serve para a maioria dos projetos
Subdomínio:
- Pode ser implantado em servidores diferentes
- É adequado para grandes sites internacionais, com operação independente por mercado
- Exige mais gestão de domínio e custos adicionais
Conclusão: escolha subdiretórios, a menos que haja uma necessidade específica.
P5: como lidar com conteúdo traduzido por máquina?
Não é recomendável usar tradução automática diretamente para SEO:
- Os buscadores reconhecem traduções de baixa qualidade, o que prejudica o posicionamento
- O conteúdo pode ser interpretado como pouco valioso
- A experiência piora e a taxa de rejeição aumenta
Se o orçamento for limitado:
- Priorize páginas essenciais, como home, produtos principais e páginas de alto tráfego
- Depois da tradução automática, faça obrigatoriamente uma revisão humana
- Melhore a tradução gradualmente e atualize o conteúdo com regularidade
P6: quanto tempo um site multilíngue leva para ser indexado?
Uma linha do tempo comum é:
- A indexação começa de uma a duas semanas depois do envio do Sitemap
- A indexação completa pode levar de um a dois meses
- A consolidação de autoridade de SEO leva de três a seis meses
Para acelerar:
- Garanta que o Sitemap esteja correto e seja enviado rapidamente
- Melhore a qualidade e a frequência de atualização do conteúdo
- Conquiste links externos de qualidade
- Solicite a indexação de páginas importantes no Google Search Console
9. Conclusão
O SEO multilíngue é decisivo para o sucesso de um site internacional. Estes são os pontos principais:
9.1 Pontos essenciais
-
Estratégia de URL
- Prefira subdiretórios, como
example.com/en/eexample.com/zh/ - Mantenha uma estrutura clara, consistente e fácil de entender
- Prefira subdiretórios, como
-
Configuração de hreflang
- Inclua em cada página todas as versões de idioma
- Inclua autorreferência
- Adicione x-default para o idioma padrão
- Use códigos de idioma corretos conforme a ISO 639-1
-
Sitemap
- Inclua as URLs de todas as versões
- Adicione informações de hreflang ao Sitemap quando possível
- Atualize-o e envie-o regularmente aos buscadores
-
Qualidade do conteúdo
- Não publique traduções automáticas sem revisão
- Preserve consistência e qualidade profissional entre os idiomas
- Localize o conteúdo, considerando cultura e hábitos de expressão
-
Monitoramento e manutenção
- Use o Google Search Console continuamente
- Verifique erros e avisos de hreflang regularmente
- Acompanhe desempenho e conversões por idioma
9.2 Checklist de ação
Conclua estas etapas para garantir uma configuração correta:
- Escolher e implementar uma estratégia de URL, de preferência por subdiretórios
- Adicionar tags hreflang completas a todas as páginas
- Configurar corretamente as tags canonical
- Gerar um Sitemap com todas as versões de idioma
- Configurar o robots.txt para apontar ao sitemap.xml
- Enviar o Sitemap ao Google Search Console e ao Bing Webmaster Tools
- Validar a configuração de hreflang
- Revisar e melhorar a qualidade das traduções
- Definir um processo de monitoramento periódico
9.3 Leituras complementares
- Guia do Google para sites multilíngues e multirregionais — documentação oficial do Google
- Guia completo de hreflang — documentação oficial do Google sobre hreflang
- Roteamento internacionalizado no Next.js — documentação oficial do Next.js
- Marcação multilíngue do Schema.org — suporte multilíngue em dados estruturados
Implementar corretamente o SEO multilíngue exige tempo e atenção, mas traz benefícios importantes: melhor posicionamento, usuários direcionados à versão correta e mais conversões. Seguindo estas práticas, seu site multilíngue terá condições melhores de aparecer nos buscadores.
Processo completo de configuração de SEO multilíngue no Next.js
Etapas completas para configurar tags hreflang, gerar um Sitemap multilíngue e escolher uma estratégia de URL
⏱️ Estimated time: 2 hr
- 1
Step 1: Configurar as tags hreflang
Configure nos metadados:
```tsx
// app/[locale]/about/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale } = params
return {
title: 'About Us',
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about', // idioma padrão
},
},
}
}
```
Pontos principais:
• inclua todas as versões de idioma
• aponte x-default para o idioma padrão
• configure todas as páginas
HTML gerado:
```html
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
```
Isso serve para:
• informar aos buscadores o idioma de destino da página
• evitar problemas de conteúdo duplicado
• combinar a página certa com cada usuário - 2
Step 2: Gerar um Sitemap multilíngue
Método 1: gere um Sitemap separado para cada idioma
```tsx
// app/[locale]/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
const locale = params.locale
return [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
// ...
]
}
```
Método 2: use um índice de Sitemaps
```tsx
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const locales = ['zh', 'en']
const baseUrl = 'https://example.com'
return locales.flatMap(locale => [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
// ...
])
}
```
Pontos principais:
• inclua todas as versões de idioma
• use o formato de URL correto
• envie o Sitemap ao Google Search Console - 3
Step 3: Escolher uma estratégia de URL
Opção 1: subdiretórios (recomendada)
• formato: /zh/about e /en/about
• configuração simples
• favorável ao SEO
• adequada para a maioria dos projetos
Opção 2: subdomínios
• formato: zh.example.com e en.example.com
• exige a configuração de vários subdomínios
• oferece mais independência
• adequada para projetos grandes
Opção 3: Cookie
• troca de idioma por Cookie
• a URL não contém um prefixo de idioma
• desfavorável ao SEO
• não recomendada
Sugestão de escolha:
• maioria dos projetos → subdiretórios
• projetos grandes → subdomínios
• evite → Cookie
Ponto principal: a estratégia por subdiretórios é a mais favorável ao SEO e, por isso, a recomendada. - 4
Step 4: Validar e testar
Ferramentas de validação:
1. Google Search Console:
• envie o Sitemap multilíngue
• verifique as tags hreflang
• acompanhe o status de indexação
2. Ferramenta de teste de hreflang:
• https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/
• confira se a configuração está correta
3. Validação do Sitemap multilíngue:
• verifique o formato do Sitemap
• confirme que todas as versões de idioma estão incluídas
• valide se as URLs estão corretas
Erros comuns a verificar:
• tags hreflang ausentes
• x-default incorreto
• Sitemap sem todas as versões de idioma
• formatos de URL inconsistentes
Recomendação: valide assim que terminar a configuração, sem esperar que os problemas apareçam.
FAQ
O que é a tag hreflang e por que ela é necessária?
Principais funções:
1. Evitar problemas de conteúdo duplicado — informa que as versões em idiomas diferentes são traduções do mesmo conteúdo
2. Combinar a versão correta com o usuário — mostra a página mais adequada conforme o idioma e a região
3. Melhorar a experiência — evita que o usuário acesse conteúdo no idioma errado
Configuração:
```tsx
export async function generateMetadata({ params }): Promise<Metadata> {
return {
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about',
},
},
}
}
```
Pontos principais:
• inclua todas as versões de idioma
• aponte x-default para o idioma padrão
• configure todas as páginas
Segundo dados do Google, mais de 60% dos sites multilíngues têm erros na configuração de hreflang.
Como configurar as tags hreflang?
```tsx
// app/[locale]/about/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale } = params
return {
title: 'About Us',
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about', // idioma padrão
},
},
}
}
```
HTML gerado:
```html
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
```
Pontos principais:
• inclua todas as versões de idioma
• aponte x-default para o idioma padrão
• configure todas as páginas
• use URLs absolutas
Atenção: as tags hreflang devem incluir todas as versões, inclusive a própria página.
Como gerar um Sitemap multilíngue?
```tsx
// app/[locale]/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
const locale = params.locale
return [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
]
}
```
Método 2: use um índice de Sitemaps
```tsx
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const locales = ['zh', 'en']
const baseUrl = 'https://example.com'
return locales.flatMap(locale => [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
])
}
```
Pontos principais:
• inclua todas as versões de idioma
• use o formato de URL correto
• envie ao Google Search Console
Recomendação: use um índice de Sitemaps, pois ele é mais flexível.
Como escolher uma estratégia de URL para um site multilíngue?
Opção 1: subdiretórios (recomendada)
• formato: /zh/about e /en/about
• configuração simples
• favorável ao SEO
• adequada para a maioria dos projetos
Opção 2: subdomínios
• formato: zh.example.com e en.example.com
• exige a configuração de vários subdomínios
• oferece mais independência
• adequada para projetos grandes
Opção 3: Cookie
• troca de idioma por Cookie
• a URL não contém um prefixo de idioma
• desfavorável ao SEO
• não recomendada
Sugestão de escolha:
• maioria dos projetos → subdiretórios
• projetos grandes → subdomínios
• evite → Cookie
Ponto principal: a estratégia por subdiretórios é a mais favorável ao SEO.
Atenção: depois de escolher a estratégia, mantenha o mesmo formato de URL nas tags hreflang.
Como validar uma configuração de SEO multilíngue?
1. Google Search Console:
• envie o Sitemap multilíngue
• verifique as tags hreflang
• acompanhe o status de indexação
2. Ferramenta de teste de hreflang:
• https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/
• confira se a configuração está correta
3. Validação do Sitemap multilíngue:
• verifique o formato
• confirme que todas as versões de idioma estão incluídas
• valide as URLs
Erros comuns a verificar:
• tags hreflang ausentes
• x-default incorreto
• Sitemap sem todas as versões de idioma
• formatos de URL inconsistentes
Recomendações:
• valide logo após configurar
• confira regularmente o status de indexação
• corrija os problemas rapidamente
Lembre-se: a validação é uma etapa importante da otimização de SEO.
Quais são os erros mais comuns no SEO multilíngue?
1. Tags hreflang ausentes
• o buscador não identifica o idioma da página
• pode exibir a versão no idioma errado
2. x-default incorreto
• x-default não foi configurado
• ou aponta para o idioma errado
3. Sitemap sem todas as versões de idioma
• apenas alguns idiomas foram enviados
• o buscador não consegue descobrir todas as páginas
4. Formatos de URL inconsistentes
• as URLs das tags hreflang não seguem o mesmo formato
• isso invalida a configuração
5. Problemas de conteúdo duplicado
• hreflang não foi configurado corretamente
• o buscador interpreta versões em idiomas diferentes como conteúdo duplicado
Como resolver:
• configure as tags hreflang
• inclua todas as versões de idioma
• use o formato de URL correto
• envie um Sitemap completo
Recomendação: siga as práticas deste guia para evitar esses erros.
22 min de leitura · Publicado em: 25 dez 2025 · Atualizado em: 8 set 2026
Guia completo Next.js
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Next.js internacionalização e geração estática: guia prático de site multilíngue com SSG
Do erro de build à otimização de desempenho: passo a passo para implementar geração estática multilíngue sem armadilhas com App Router. Inclui exemplos completos, detalhes de generateStaticParams e técnicas para reduzir tempo de build.
Parte 9 de 26
Próximo
Guia completo de API Routes no Next.js: de Route Handlers às melhores práticas de tratamento de erros
Guia completo de API Routes no Next.js: aprenda a criar Route Handlers, processar requisições, tratar erros e projetar respostas para desenvolver APIs de backend com Next.js.
Parte 11 de 26



Comentários
Entre com GitHub para comentar