Назад

Как оптимизировать SEO для многоязычного приложения Next.js

General Translation avatarGeneral Translation
guideseointernationalizationnextjsi18nroutingmetadata

Почему многоязычному SEO нужно уделять особое внимание

Большинство разработчиков, добавляя i18n в своё приложение Next.js, сосредотачиваются на переводе интерфейса — и на этом останавливаются. Но если поисковые системы не могут найти, проиндексировать и корректно связать ваши языковые версии, вся эта работа по переводу остаётся невидимой.

У многоязычного сайта без грамотной SEO-настройки появляются проблемы:

  • Google может проиндексировать только одну языковую версию и проигнорировать остальные
  • Пользователям, которые ищут по‑испански, показывается страница на английском
  • Санкции за дублирующийся контент, потому что Google видит /en/about и /fr/about как одну и ту же страницу
  • Неправильный язык в сниппетах результатов поиска

Хорошая новость: правильно настроить многоязычное SEO в Next.js несложно. Есть пять вещей, которые нужно сделать правильно, и это руководство охватывает их все, используя gt-next.


1. Маршрутизация URL на основе локали

Основа многоязычного SEO — наличие отдельных URL-адресов для каждого языка. Поисковым системам нужны отдельные, доступные для обхода URL-адреса, чтобы независимо индексировать каждую языковую версию.

Это означает локаль-в-URL — а не cookies, не параметры запроса и не одно лишь определение Accept-Language.

✅ generaltranslation.com/en/about
✅ generaltranslation.com/fr/about
✅ generaltranslation.com/es/about

❌ generaltranslation.com/about?lang=fr
❌ generaltranslation.com/about (с локалью в cookie)

Настройка маршрутизации по локалям с помощью gt-next

Сначала разместите страницы внутри динамического сегмента [locale]:

app/
└── [locale]/
    ├── layout.tsx
    ├── page.tsx
    └── about/
        └── page.tsx

Затем создайте middleware в корневой директории проекта (proxy.ts для Next.js 16+ или middleware.ts для Next.js 15 и более ранних версий):

import { createNextMiddleware } from 'gt-next/middleware';

export default createNextMiddleware();

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};

Это автоматически даёт вам URL с префиксом локали. По умолчанию локаль по умолчанию (например, английский) не получает префикс — /about остаётся без префикса, в то время как пользователи, говорящие по‑испански, видят /es/about, а франкоязычные пользователи — /fr/about.


2. Настройка атрибута lang в HTML

Атрибут lang в теге <html> сообщает браузерам и поисковым системам, на каком языке представлена страница. Это одна из самых простых и при этом наиболее эффективных вещей, которые вы можете сделать для доступности и SEO.

Без него скринридеры пытаются угадать язык (и часто ошибаются), а у поисковых систем меньше уверенности в своей языковой классификации.

gt-next предоставляет хук useLocale, который делает это предельно простым в вашем корневом layout-е:

import { useLocale, GTProvider } from 'gt-next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = useLocale();

  return (
    <html lang={locale}>
      <body>
        <GTProvider>
          {children}
        </GTProvider>
      </body>
    </html>
  );
}

useLocale возвращает код локали в формате BCP 47 (например, en-US, ar, zh-Hans).


3. Канонические URL

Канонические теги указывают поисковым системам, какой URL является основной версией страницы. Для многоязычных сайтов каждая языковая версия должна указывать саму на себя в качестве канонической:

<!-- На /fr/about -->
<link rel="canonical" href="https://example.com/fr/about" />

Это предотвращает то, что поисковые системы будут считать вашу французскую страницу дубликатом английской.

В Next.js вы задаёте канонические URL-адреса через metadata API. Используйте его вместе с getLocale из gt-next, чтобы генерировать корректный канонический URL-адрес для каждой локали:

import { getLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';

export async function generateMetadata() {
  const locale = await getLocale();

  return {
    alternates: {
      canonical: `${BASE_URL}/${locale}/about`,
    },
  };
}

export default function AboutPage() {
  return <h1>About Us</h1>;
}

Для локали по умолчанию без префикса настройте соответствующим образом:

import { getLocale, getDefaultLocale } from 'gt-next/server';

export async function generateMetadata() {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();
  const path = '/about';

  const prefix = locale === defaultLocale ? '' : `/${locale}`;

  return {
    alternates: {
      canonical: `${BASE_URL}${prefix}${path}`,
    },
  };
}

4. Теги hreflang

Теги hreflang — самый важный сигнал для многоязычной SEO-оптимизации. Они сообщают поисковым системам: «эта страница существует на других языках, и вот их URL-адреса».

Без hreflang Google вынужден угадывать, какую языковую версию показывать в результатах поиска — и часто ошибается.

<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="es" href="https://example.com/es/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />

Тег x-default указывает поисковым системам, какой URL показывать, когда ни один из указанных языков не соответствует пользователю.

В Next.js вы можете добавить hreflang через свойство alternates.languages API метаданных. Вот переиспользуемый хелпер, который работает с gt-next:

import { getLocale, getDefaultLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';
const SUPPORTED_LOCALES = ['en', 'fr', 'es'];

export async function getI18NMetadata(path: string) {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();

  const getUrl = (loc: string) => {
    const prefix = loc === defaultLocale ? '' : `/${loc}`;
    return `${BASE_URL}${prefix}${path}`;
  };

  const languages: Record<string, string> = {};
  for (const loc of SUPPORTED_LOCALES) {
    languages[loc] = getUrl(loc);
  }
  languages['x-default'] = getUrl(defaultLocale);

  return {
    alternates: {
      canonical: getUrl(locale),
      languages,
    },
  };
}

Теперь используйте его на любой странице:

import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  return await getI18NMetadata('/about');
}

При одном вызове генерируются и канонический тег, и все теги hreflang.


5. Переведённые метаданные

Поисковые системы отображают заголовок и описание вашей страницы в результатах поиска. Если для франкоязычной страницы они указаны на английском, пользователи с меньшей вероятностью перейдут по такому результату — и Google может понизить его в поисковой выдаче.

Используйте функцию getGT из gt-next для перевода строк метаданных:

import { getGT } from 'gt-next/server';
import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  const t = await getGT();
  const i18nMeta = await getI18NMetadata('/about');

  return {
    title: t('О нас'),
    description: t('Узнайте больше о нашей миссии и команде.'),
    ...i18nMeta,
  };
}

В итоге вы получаете локализованные заголовки и описания в результатах поиска, что значительно повышает показатель переходов по кликам для неанглоязычных запросов.


6. Многоязычные sitemap-файлы

Файл sitemap помогает поисковым системам находить все страницы вашего сайта, включая версии на разных языках. Для многоязычных сайтов рекомендуется также включать атрибуты hreflang в sitemap.

Next.js поддерживает программно формируемые sitemap-файлы через файл sitemap.ts:

import { MetadataRoute } from 'next';

const BASE_URL = 'https://example.com';
const LOCALES = ['en', 'fr', 'es'];
const DEFAULT_LOCALE = 'en';
const PAGES = ['/', '/about', '/blog', '/contact'];

export default function sitemap(): MetadataRoute.Sitemap {
  return PAGES.flatMap((path) => {
    const getUrl = (locale: string) => {
      const prefix = locale === DEFAULT_LOCALE ? '' : `/${locale}`;
      return `${BASE_URL}${prefix}${path === '/' ? '' : path}`;
    };

    const languages: Record<string, string> = {};
    for (const locale of LOCALES) {
      languages[locale] = getUrl(locale);
    }

    return LOCALES.map((locale) => ({
      url: getUrl(locale),
      lastModified: new Date(),
      alternates: { languages },
    }));
  });
}

Это создаёт карту сайта с одной записью на каждую страницу и каждую локаль, и в каждую запись включаются аннотации hreflang, указывающие на все языковые версии.


Чеклист

Краткое резюме всего, что мы рассмотрели:

SEO-требованиеРеализация
Локаль в URLcreateNextMiddleware() с динамическим сегментом [locale]
Атрибут HTML languseLocale() в корневом layout'е
Канонические URLgetLocale() + Next.js metadata API alternates.canonical
Теги hreflangNext.js metadata API alternates.languages со всеми поддерживаемыми языками и локалями
Переведённые метаданныеgetGT() для заголовков страниц и описаний
Мультиязычный sitemapsitemap.ts с записями для каждой локали и альтернативами hreflang

Дальнейшие шаги