ГлавнаяПесочницаВитринаПриложениеДокументБлог
    • EnglishАнглийский
      EN
    • РусскийРусский
      RU
    • 日本語Японский
      JA
    • françaisФранцузский
      FR
    • 한국어Корейский
      KO
    • 中文Китайский
      ZH
    • EspañolИспанский
      ES
    • DeutschНемецкий
      DE
    • العربيةАрабский
      AR
    • ItalianoИтальянский
      IT
    • British EnglishБританский английский
      EN-GB
    • PortuguêsПортугальский
      PT
    • हिन्दीХинди
      HI
    • TürkçeТурецкий
      TR
    • polskiПольский
      PL
    • IndonesiaИндонезийский
      ID
    • Tiếng ViệtВьетнамский
      VI
    • УкраїнськаУкраинский
      UK
    /
    Alt+←
    Что такое интернационализация (i18n)?
    SEO и Интернационализация
    Руководство
    • i18n с помощью next-i18next
    • i18n с помощью next-intl
    Используйте Intlayer в вашем решении
    • Автоматизировать next-i18next
    • Автоматизировать react-i18next
    • Автоматизировать next-intl
    • Автоматизировать react-intl
    • Автоматизировать vue-i18n
    Сравнения
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    Документация
    1. Blog
    2. Blog seo i18n nextjs
    Creation:2025-09-28Last update:2025-09-28
    Ссылайтесь на этот документ на ваш любимый ассистент AI
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Спросите свой вопрос и получите сводку документа, используя эту страницу и выбранного вами поставщика AI

    Содержимое этой страницы было переведено с помощью ИИ.

    Смотреть последнюю версию оригинального контента на английском
    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 и i18n в Next.js: Перевода недостаточно

    Когда разработчики думают об интернационализации (i18n), их первой реакцией часто является: перевести контент. Но обычно забывают, что главная цель интернационализации, сделать ваш сайт более заметным для всего мира. Если ваше многоязычное приложение Next.js не сообщает поисковым системам, как сканировать и понимать различные языковые версии, большая часть ваших усилий может остаться незамеченной.

    В этом блоге мы рассмотрим, почему i18n, это суперсила SEO, и как правильно реализовать её в Next.js с помощью next-intl, next-i18next и Intlayer.


    Почему SEO и i18n

    Добавление языков, это не только про удобство пользователя (UX). Это также мощный рычаг для органической видимости. Вот почему:

    1. Лучшая обнаруживаемость: Поисковые системы индексируют локализованные версии и ранжируют их для пользователей, ищущих на своем родном языке.
    2. Избежание дублированного контента: Правильные канонические и альтернативные теги сообщают поисковым роботам, какая страница относится к какому языку.
    3. Лучший UX: Посетители сразу попадают на правильную версию вашего сайта.
    4. Конкурентное преимущество: Немногие сайты хорошо реализуют многоязычное SEO, что даёт вам возможность выделиться.

    Лучшие практики многоязычного SEO в Next.js

    Вот чеклист, который должно реализовать каждое многоязычное приложение:

    • Устанавливайте метатеги hreflang в <head>
      Помогает Google понять, какие версии существуют для каждого языка.

    • Включайте все переведённые страницы в sitemap.xml
      Используйте схему xhtml, чтобы поисковые роботы могли легко находить альтернативные версии.

    • Исключайте приватные/локализованные маршруты в robots.txt
      Например, не позволяйте индексировать /dashboard, /fr/dashboard, /es/dashboard.

    • Используйте локализованные ссылки
      Пример: <a href="/fr/about">À propos</a> вместо ссылки на стандартную /about.

    Это простые шаги, но их пропуск может стоить вам видимости.


    Примеры реализации

    Разработчики часто забывают правильно ссылаться на свои страницы в разных локалях, поэтому давайте посмотрим, как это работает на практике с различными библиотеками.

    next-intl

    src/app/[locale]/about/layout.tsx
    Копировать код

    Копировать код в буфер обмена

    import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";// Функция для получения локализованного путиfunction localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// Получаем переводы для текущей локали и namespace "about"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 },  },};}// ... Остальная часть кода страницы
    src/app/sitemap.ts
    Копировать код

    Копировать код в буфер обмена

    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
    Копировать код

    Копировать код в буфер обмена

    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`,};}  rules: { userAgent: "*", allow: ["/"], disallow },  host: origin,  sitemap: `${origin}/sitemap.xml`,};}

    next-i18next

    i18n.config.ts
    Копировать код

    Копировать код в буфер обмена

    export const locales = ["en", "fr"] as const;export type Locale = (typeof locales)[number];export const defaultLocale: Locale = "en";/** Добавляет префикс локали к пути, если это не локаль по умолчанию */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** Помощник для абсолютного URL */const ORIGIN = "https://example.com";export function abs(locale: string, path: string) {return `${ORIGIN}${localizedPath(locale, path)}`;}
    src/app/[locale]/about/layout.tsx
    Копировать код

    Копировать код в буфер обмена

    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;// Динамически импортировать правильный JSON-файлconst 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>О нас</h1>;}
    src/app/sitemap.ts
    Копировать код

    Копировать код в буфер обмена

    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
    Копировать код

    Копировать код в буфер обмена

    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
    Копировать код

    Копировать код в буфер обмена

    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);/** * Генерирует объект, содержащий все URL для каждого языка. * * Пример: * ```ts *  getMultilingualUrls('/about'); * *  // Возвращает *  // { *  //   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" },  },};};// ... Остальной код страницы
    src/app/sitemap.ts
    Копировать код

    Копировать код в буфер обмена

    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
    Копировать код

    Копировать код в буфер обмена

    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";// Функция для получения всех многоязычных URL из массива URLconst getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// Конфигурация robots.txt с правилами для поисковых роботовconst robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*", // Правила применяются ко всем роботам  allow: ["/"], // Разрешенные пути  disallow: getAllMultilingualUrls(["/dashboard"]), // Запрещенные пути (все языковые версии /dashboard)},host: "https://example.com", // Хост сайтаsitemap: `https://example.com/sitemap.xml`, // Путь к карте сайта});export default robots;
    Intlayer предоставляет функцию getMultilingualUrls для генерации многоязычных URL-адресов для вашей карты сайта.

    Заключение

    Правильная реализация i18n в Next.js, это не просто перевод текста, а обеспечение того, чтобы поисковые системы и пользователи точно знали, какую версию вашего контента показывать. Настройка hreflang, карт сайта и правил для robots, это то, что превращает переводы в реальную SEO-ценность.

    Хотя next-intl и next-i18next предоставляют надежные способы для этого, они обычно требуют много ручной настройки, чтобы поддерживать согласованность между локалями.

    Именно здесь Intlayer действительно выделяется:

    Он поставляется с встроенными помощниками, такими как getMultilingualUrls, что делает интеграцию hreflang, карты сайта и robots практически без усилий.

    Метаданные остаются централизованными, а не разбросанными по JSON-файлам или пользовательским утилитам.

    Он разработан специально для Next.js с нуля, поэтому вы тратите меньше времени на отладку конфигурации и больше времени на выпуск продукта.

    Если ваша цель, не просто переводить, а масштабировать многоязычное SEO без лишних сложностей, Intlayer предоставляет вам самое чистое и перспективное решение.

    Что такое интернационализация (i18n)?
    Alt+→

    На этой странице

      Обсуждения анонимны и регулярно просматриваются для решения распространённых проблем. Не стесняйтесь делиться идеями функций, отзывами о документации или чем-либо, связанным с Intlayer, мы используем эту информацию для формирования нашей дорожной карты и улучшения продукта.

      import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations, unstable_setRequestLocale } from "next-intl/server";// Функция для получения локализованного путиfunction localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}export async function generateMetadata({params,}: {params: { locale: string };}): Promise<Metadata> {const { locale } = params;// Получаем переводы для текущей локали и namespace "about"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 },  },};}// ... Остальная часть кода страницы
      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`,};}  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";/** Добавляет префикс локали к пути, если это не локаль по умолчанию */export function localizedPath(locale: string, path: string) {return locale === defaultLocale ? path : `/${locale}${path}`;}/** Помощник для абсолютного URL */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;// Динамически импортировать правильный JSON-файлconst 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>О нас</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);/** * Генерирует объект, содержащий все URL для каждого языка. * * Пример: * ```ts *  getMultilingualUrls('/about'); * *  // Возвращает *  // { *  //   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" },  },};};// ... Остальной код страницы
      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";// Функция для получения всех многоязычных URL из массива URLconst getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);// Конфигурация robots.txt с правилами для поисковых роботовconst robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*", // Правила применяются ко всем роботам  allow: ["/"], // Разрешенные пути  disallow: getAllMultilingualUrls(["/dashboard"]), // Запрещенные пути (все языковые версии /dashboard)},host: "https://example.com", // Хост сайтаsitemap: `https://example.com/sitemap.xml`, // Путь к карте сайта});export default robots;