ГоловнаПісочницяВітринаДодатокДокументаціяБлог
    • 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
    Гід
    • 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

    Задайте питання та отримайте підсумок документа, вказавши цю сторінку та обраного вами постачальника штучного інтелекту

    Вміст цієї сторінки перекладено за допомогою штучного інтелекту.

    Переглянути останню версію оригінального вмісту англійською
    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">Про нас</a> замість посилання на сторінку за замовчуванням /about.

    Це прості кроки, але їх пропуск може коштувати вам видимості.


    Приклади реалізації

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

    next-intl

    Копіювати код

    Скопіюйте код у буфер обміну

    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;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`,};}

    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";const getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*",  allow: ["/"],  disallow: getAllMultilingualUrls(["/dashboard"]),},host: "https://example.com",sitemap: `https://example.com/sitemap.xml`,});export default robots;
    Intlayer надає функцію getMultilingualUrls для генерації багатомовних URL-адрес для вашого sitemap.

    Висновок

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

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

    Саме тут Intlayer дійсно вирізняється:

    Воно постачається з вбудованими хелперами, такими як getMultilingualUrls, що робить інтеграцію hreflang, sitemap і 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;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`,};}
      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";const getAllMultilingualUrls = (urls: string[]) =>urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({rules: {  userAgent: "*",  allow: ["/"],  disallow: getAllMultilingualUrls(["/dashboard"]),},host: "https://example.com",sitemap: `https://example.com/sitemap.xml`,});export default robots;