InícioAmbiente de testeVitrineAppDocBlog
    • EnglishInglês
      EN
    • РусскийRusso
      RU
    • 日本語Japonês
      JA
    • françaisFrancês
      FR
    • 한국어Coreano
      KO
    • 中文Chinês
      ZH
    • EspañolEspanhol
      ES
    • DeutschAlemão
      DE
    • العربيةÁrabe
      AR
    • ItalianoItaliano
      IT
    • British EnglishInglês (Reino Unido)
      EN-GB
    • PortuguêsPortuguês
      PT
    • हिन्दीHíndi
      HI
    • TürkçeTurco
      TR
    • polskiPolonês
      PL
    • IndonesiaIndonésio
      ID
    • Tiếng ViệtVietnamita
      VI
    • УкраїнськаUcraniano
      UK
    /
    Alt+←
    O que é internacionalização (i18n)?
    SEO e i18n
    Guia
    • i18n com next-i18next
    • i18n com next-intl
    Use Intlayer na sua solução
    • Automatizar next-i18next
    • Automatizar react-i18next
    • Automatizar next-intl
    • Automatizar react-intl
    • Automatizar vue-i18n
    Comparações
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    Documentação
    1. Blog
    2. Blog seo i18n nextjs
    Creation:2025-09-28Last update:2025-09-28
    Referência esta documentação ao seu assistente AI favorito
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Faça sua pergunta e obtenha um resumo do documento referenciando esta página e o provedor AI de sua escolha

    O conteúdo desta página foi traduzido com uma IA.

    Veja a última versão do conteúdo original em inglês
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    SEO e i18n no Next.js: Traduzir não é suficiente

    Quando os desenvolvedores pensam em internacionalização (i18n), o primeiro reflexo é frequentemente: traduzir o conteúdo. Mas as pessoas geralmente esquecem que o principal objetivo da internacionalização é tornar seu site mais visível para o mundo. Se o seu app Next.js multilíngue não informar aos motores de busca como rastrear e entender suas diferentes versões de idioma, a maior parte do seu esforço pode passar despercebida.

    Neste blog, vamos explorar por que a i18n é uma superpotência do SEO e como implementá-la corretamente no Next.js com next-intl, next-i18next e Intlayer.


    Por que SEO e i18n

    Adicionar idiomas não é apenas sobre UX. É também uma alavanca poderosa para a visibilidade orgânica. Eis o porquê:

    1. Melhor descobribilidade: Os motores de busca indexam versões localizadas e as classificam para usuários que buscam em seu idioma nativo.
    2. Evitar conteúdo duplicado: Tags canônicas e alternadas adequadas indicam aos rastreadores qual página pertence a qual localidade.
    3. Melhor UX: Os visitantes chegam imediatamente à versão correta do seu site.
    4. Vantagem competitiva: Poucos sites implementam SEO multilíngue corretamente, o que significa que você pode se destacar.

    Melhores práticas para SEO multilíngue no Next.js

    Aqui está uma lista de verificação que todo app multilíngue deve implementar:

    • Defina as meta tags hreflang no <head>
      Ajuda o Google a entender quais versões existem para cada idioma.

    • Liste todas as páginas traduzidas no sitemap.xml
      Use o esquema xhtml para que os rastreadores encontrem facilmente as versões alternativas.

    • Exclua rotas privadas/localizadas no robots.txt
      Exemplo: não permita que /dashboard, /fr/dashboard, /es/dashboard sejam indexados.

    • Use links localizados
      Exemplo: <a href="/fr/about">À propos</a> em vez de linkar para o padrão /about.

    Estes são passos simples, mas ignorá-los pode custar sua visibilidade.


    Exemplos de Implementação

    Os desenvolvedores frequentemente esquecem de referenciar corretamente suas páginas entre os idiomas, então vamos ver como isso funciona na prática com diferentes bibliotecas.

    next-intl

    src/app/[locale]/about/layout.tsx
    Copiar código

    Copiar o código para a área de transferência

    import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";// Função para gerar o caminho localizado conforme o idiomafunction localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;const t = await getTranslations({ locale, namespace: "about" });const url = "/about";const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, url)]));return {  title: t("title"),  description: t("description"),  alternates: {    canonical: localizedPath(locale, url),    languages: { ...languages, "x-default": url },  },};}// ... Resto do código da página
    src/app/sitemap.ts
    Copiar código

    Copiar o código para a área de transferência

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;export default function sitemap(): MetadataRoute.Sitemap {const aboutLanguages = Object.fromEntries(  locales.map((l) => [l, formatterLocalizedPath(l, "/about")]));return [  {    url: formatterLocalizedPath(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages: aboutLanguages },  },];}
    src/app/robots.ts
    Copiar código

    Copiar o código para a área de transferência

    import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const withAllLocales = (path: string) => [path,...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...withAllLocales("/dashboard"),  ...withAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: origin,  sitemap: `${origin}/sitemap.xml`,};}

    next-i18next

    i18n.config.ts
    Copiar código

    Copiar o código para a área de transferência

    export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";/** Prefixa o caminho com o locale, a menos que seja o locale padrão */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** Helper para URL absoluta */const ORIGIN = "https://example.com";export function abs(locale: string, path: string) {return `${ORIGIN}${localizedPath(locale, path)}`;}
    src/app/[locale]/about/layout.tsx
    Copiar código

    Copiar o código para a área de transferência

    import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// Importa dinamicamente o arquivo JSON corretoconst messages = (await import(`@/../public/locales/${locale}/about.json`))  .default;const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, "/about")]));return {  title: messages.title,  description: messages.description,  alternates: {    canonical: localizedPath(locale, "/about"),    languages: { ...languages, "x-default": "/about" },  },};}export default async function AboutPage() {return <h1>Sobre</h1>;}
    src/app/sitemap.ts
    Copiar código

    Copiar o código para a área de transferência

    import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap {const languages = Object.fromEntries(  locales.map((l) => [l, abs(l, "/about")]));return [  {    url: abs(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages },  },];}
    src/app/robots.ts
    Copiar código

    Copiar o código para a área de transferência

    import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";const expandAllLocales = (path: string) => [localizedPath(defaultLocale, path),...locales  .filter((l) => l !== defaultLocale)  .map((l) => localizedPath(l, path)),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...expandAllLocales("/dashboard"),  ...expandAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: ORIGIN,  sitemap: `${ORIGIN}/sitemap.xml`,};}

    Intlayer

    src/app/[locale]/about/layout.tsx
    Copiar código

    Copiar o código para a área de transferência

    import { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({params,}: LocalPromiseParams): Promise<Metadata> => {const { locale } = await params;const metadata = getIntlayer("page-metadata", locale);/** * Gera um objeto contendo todas as URLs para cada localidade. * * Exemplo: * ```ts *  getMultilingualUrls('/about'); * *  // Retorna *  // { *  //   en: '/about', *  //   fr: '/fr/about', *  //   es: '/es/about', *  // } * ``` */const multilingualUrls = getMultilingualUrls("/about");return {  ...metadata,  alternates: {    canonical: multilingualUrls[locale as keyof typeof multilingualUrls],    languages: { ...multilingualUrls, "x-default": "/about" },  },};};// ... Resto do código da página
    src/app/sitemap.ts
    Copiar código

    Copiar o código para a área de transferência

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [{  url: "https://example.com/about",  alternates: {    languages: { ...getMultilingualUrls("https://example.com/about") },  },},];
    src/app/robots.ts
    Copiar código

    Copiar o código para a área de transferência

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// Função para obter todas as URLs multilíngues a partir de uma lista de URLsconst getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*",  allow: ["/"],  disallow: getAllMultilingualUrls(["/dashboard"]), // Bloqueia o acesso multilíngue à rota /dashboard},host: "https://example.com",sitemap: `https://example.com/sitemap.xml`,});export default robots;
    Intlayer fornece uma função getMultilingualUrls para gerar URLs multilíngues para o seu sitemap.

    Conclusão

    Fazer a i18n corretamente no Next.js não é apenas sobre traduzir texto, mas sim garantir que os motores de busca e os usuários saibam exatamente qual versão do seu conteúdo deve ser exibida. Configurar hreflang, sitemaps e regras de robots é o que transforma traduções em valor real para SEO.

    Enquanto next-intl e next-i18next oferecem formas sólidas de configurar isso, geralmente exigem muita configuração manual para manter a consistência entre os locais.

    É aqui que o Intlayer realmente se destaca:

    Ele vem com helpers integrados como getMultilingualUrls, tornando a integração de hreflang, sitemap e robots quase sem esforço.

    Os metadados permanecem centralizados em vez de dispersos em arquivos JSON ou utilitários personalizados.

    Foi projetado para Next.js desde o início, para que você gaste menos tempo depurando configurações e mais tempo entregando.

    Se o seu objetivo não é apenas traduzir, mas escalar o SEO multilíngue sem atritos, o Intlayer oferece a configuração mais limpa e preparada para o futuro.

    O que é internacionalização (i18n)?
    Alt+→

    Nesta página

      As discussões são anônimas e regularmente revisadas para resolver problemas comuns. Sinta-se à vontade para compartilhar ideias de funcionalidades, feedback sobre a documentação ou qualquer coisa relacionada ao Intlayer, usamos essas informações para moldar nosso roadmap e melhorar o produto.

      import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";// Função para gerar o caminho localizado conforme o idiomafunction localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;const t = await getTranslations({ locale, namespace: "about" });const url = "/about";const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, url)]));return {  title: t("title"),  description: t("description"),  alternates: {    canonical: localizedPath(locale, url),    languages: { ...languages, "x-default": url },  },};}// ... Resto do código da página
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) =>locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;export default function sitemap(): MetadataRoute.Sitemap {const aboutLanguages = Object.fromEntries(  locales.map((l) => [l, formatterLocalizedPath(l, "/about")]));return [  {    url: formatterLocalizedPath(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages: aboutLanguages },  },];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const withAllLocales = (path: string) => [path,...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...withAllLocales("/dashboard"),  ...withAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: origin,  sitemap: `${origin}/sitemap.xml`,};}
      export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";/** Prefixa o caminho com o locale, a menos que seja o locale padrão */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** Helper para URL absoluta */const ORIGIN = "https://example.com";export function abs(locale: string, path: string) {return `${ORIGIN}${localizedPath(locale, path)}`;}
      import type { Metadata } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// Importa dinamicamente o arquivo JSON corretoconst messages = (await import(`@/../public/locales/${locale}/about.json`))  .default;const languages = Object.fromEntries(  locales.map((l) => [l, localizedPath(l, "/about")]));return {  title: messages.title,  description: messages.description,  alternates: {    canonical: localizedPath(locale, "/about"),    languages: { ...languages, "x-default": "/about" },  },};}export default async function AboutPage() {return <h1>Sobre</h1>;}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, abs } from "@/i18n.config";export default function sitemap(): MetadataRoute.Sitemap {const languages = Object.fromEntries(  locales.map((l) => [l, abs(l, "/about")]));return [  {    url: abs(defaultLocale, "/about"),    lastModified: new Date(),    changeFrequency: "monthly",    priority: 0.7,    alternates: { languages },  },];}
      import type { MetadataRoute } from "next";import { locales, defaultLocale, localizedPath } from "@/i18n.config";const ORIGIN = "https://example.com";const expandAllLocales = (path: string) => [localizedPath(defaultLocale, path),...locales  .filter((l) => l !== defaultLocale)  .map((l) => localizedPath(l, path)),];export default function robots(): MetadataRoute.Robots {const disallow = [  ...expandAllLocales("/dashboard"),  ...expandAllLocales("/admin"),];return {  rules: { userAgent: "*", allow: ["/"], disallow },  host: ORIGIN,  sitemap: `${ORIGIN}/sitemap.xml`,};}
      import { getIntlayer, getMultilingualUrls } from "intlayer";import type { Metadata } from "next";import type { LocalPromiseParams } from "next-intlayer";export const generateMetadata = async ({params,}: LocalPromiseParams): Promise<Metadata> => {const { locale } = await params;const metadata = getIntlayer("page-metadata", locale);/** * Gera um objeto contendo todas as URLs para cada localidade. * * Exemplo: * ```ts *  getMultilingualUrls('/about'); * *  // Retorna *  // { *  //   en: '/about', *  //   fr: '/fr/about', *  //   es: '/es/about', *  // } * ``` */const multilingualUrls = getMultilingualUrls("/about");return {  ...metadata,  alternates: {    canonical: multilingualUrls[locale as keyof typeof multilingualUrls],    languages: { ...multilingualUrls, "x-default": "/about" },  },};};// ... Resto do código da página
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [{  url: "https://example.com/about",  alternates: {    languages: { ...getMultilingualUrls("https://example.com/about") },  },},];
      import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// Função para obter todas as URLs multilíngues a partir de uma lista de URLsconst getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*",  allow: ["/"],  disallow: getAllMultilingualUrls(["/dashboard"]), // Bloqueia o acesso multilíngue à rota /dashboard},host: "https://example.com",sitemap: `https://example.com/sitemap.xml`,});export default robots;