Как оптимизировать SEO для многоязычного приложения Next.js
Почему многоязычному 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-требование | Реализация |
|---|---|
| Локаль в URL | createNextMiddleware() с динамическим сегментом [locale] |
Атрибут HTML lang | useLocale() в корневом layout'е |
| Канонические URL | getLocale() + Next.js metadata API alternates.canonical |
| Теги hreflang | Next.js metadata API alternates.languages со всеми поддерживаемыми языками и локалями |
| Переведённые метаданные | getGT() для заголовков страниц и описаний |
| Мультиязычный sitemap | sitemap.ts с записями для каждой локали и альтернативами hreflang |
Дальнейшие шаги
- Быстрый старт с gt-next для настройки полного i18n-стека
- Руководство по middleware по настройке маршрутизации
- Руководство по SSG по статической генерации многоязычных страниц
- Поддержка RTL для языков с письмом справа налево