Как оптимизировать 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 — не cookie, не параметры запроса и не одно лишь определение через Accept-Language.
✅ generaltranslation.com/en/about
✅ generaltranslation.com/fr/about
✅ generaltranslation.com/es/about
❌ generaltranslation.com/about?lang=fr
❌ generaltranslation.com/about (with locale in a 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. Настройка HTML-атрибута lang
Атрибут lang в теге <html> сообщает браузерам и поисковым системам, на каком языке написана страница.
Это одно из самых простых и при этом самых эффективных действий для доступности и SEO.
Без него программы экранного доступа определяют язык наугад и часто ошибаются, а поисковые системы менее уверены в правильности языковой классификации.
gt-next предоставляет Hook 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
Теги canonical сообщают поисковым системам, какой URL является "основной" версией страницы. Для многоязычных сайтов каждая языковая версия должна указывать сама на себя как на каноническую:
<!-- На /fr/about -->
<link rel="canonical" href="https://example.com/fr/about" />Это не позволяет поисковым системам считать вашу французскую страницу дубликатом английской.
В Next.js канонические URL задаются через 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');
}Это генерирует и canonical, и все теги 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('About Us'),
description: t('Learn about our mission and team.'),
...i18nMeta,
};
}Это дает вам локализованные заголовки и описания в результатах поиска, что значительно повышает CTR для запросов не на английском языке.
6. Многоязычные карты сайта
Карта сайта помогает поисковым системам находить все ваши страницы, включая версии на всех языках. Для многоязычных сайтов в карту сайта также следует добавить аннотации hreflang.
Next.js поддерживает программную генерацию карт сайта через файл 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() + API метаданных Next.js alternates.canonical |
| Теги hreflang | API метаданных Next.js alternates.languages со всеми поддерживаемыми локалями |
| Переведённые метаданные | getGT() для заголовков и описаний страниц |
| Многоязычная карта сайта | sitemap.ts с записями для каждой локали и альтернативами hreflang |
Следующие шаги
- Быстрый старт с gt-next, чтобы настроить полный стек i18n
- Руководство по middleware для настройки маршрутизации
- Руководство по SSG для статической генерации многоязычных страниц
- Поддержка RTL для языков с письмом справа налево