Alternar tema

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

Easton editorial illustration: island architecture model

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.

60%+
taxa de erro de configuração em sites multilíngues
mais de 60% dos sites multilíngues têm erros na configuração de hreflang

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:

  1. 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
  2. Combinar a página com o usuário certo — mostra a versão mais adequada conforme idioma e região
  3. 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:

  1. Detecta o idioma da página por meio do atributo HTML lang, das tags hreflang e da análise do conteúdo
  2. Procura as tags hreflang para entender a relação entre as páginas em idiomas diferentes
  3. Exibe nos resultados a versão correspondente à preferência de idioma do usuário
  4. 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égiaExemploImpacto no SEODificuldadeRecomendação
Subdiretórioexample.com/en/
example.com/zh/
⭐⭐⭐⭐⭐ Excelente⭐⭐⭐ Média⭐⭐⭐⭐⭐
Subdomínioen.example.com
zh.example.com
⭐⭐⭐ Razoável⭐⭐⭐⭐ Alta⭐⭐⭐
Parâmetro de URLexample.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:

  1. Quais versões de idioma uma página possui
  2. Qual é a URL completa de cada versão
  3. 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:

  1. Indexação mais rápida — informa ativamente todas as versões, sem depender de o rastreador encontrá-las sozinho
  2. Cobertura completa — reduz o risco de omitir idiomas, sobretudo em páginas profundas
  3. 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:

  1. Acesse o Google Search Console
  2. Selecione a propriedade do site
  3. No menu lateral, escolha “Sitemaps”
  4. Informe a URL do sitemap.xml
  5. Clique em “Enviar”

Método 3: enviar ao Bing Webmaster Tools

O Bing também tem participação relevante no mercado:

  1. Acesse o Bing Webmaster Tools
  2. Adicione o site
  3. Envie o sitemap.xml na seção de Sitemaps

4.7 Validando o Sitemap

Use estas ferramentas para conferir o formato:

  1. XML Sitemap Validator: https://www.xml-sitemaps.com/validate-xml-sitemap.html
  2. Google Search Console: consulte o status da indexação e os erros após o envio
  3. 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

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 lang correto 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-default apontando 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:

  1. Informe a URL
  2. Aguarde o rastreamento e a análise do Google
  3. Confira erros e avisos
  4. Verifique se as tags hreflang foram reconhecidas corretamente

7.3 Ferramentas para verificar hreflang

Algumas opções específicas:

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:

  1. Envie o Sitemap ao Google Search Console
  2. Aguarde de uma a duas semanas para a indexação inicial
  3. Consulte o relatório “Segmentação internacional” > “Idioma”
  4. Verifique erros e avisos de hreflang
  5. 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:

  1. Priorize páginas essenciais, como home, produtos principais e páginas de alto tráfego
  2. Depois da tradução automática, faça obrigatoriamente uma revisão humana
  3. 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

  1. Estratégia de URL

    • Prefira subdiretórios, como example.com/en/ e example.com/zh/
    • Mantenha uma estrutura clara, consistente e fácil de entender
  2. 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
  3. 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
  4. 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
  5. 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

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. 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. 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. 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. 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?
hreflang é um atributo HTML que informa aos buscadores o idioma e a região de destino de uma página.

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?
Configure-as 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
},
},
}
}
```

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?
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 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?
Há três estratégias 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.

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?
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
• 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?
Erros comuns:

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

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog