Связывание альтернативных языковых версий (hreflang)
Информирование поисковых систем о ваших локализованных страницах
Проблема
Приложение имеет идентичный контент, доступный по адресам /en/page и /fr/page. Поисковые системы рассматривают их как две отдельные конкурирующие страницы. Без механизма для их связывания рейтинги в поиске разделяются, и пользователи во Франции могут видеть английскую страницу в результатах поиска вместо французской.
Решение
Используйте свойство alternates в функции generateMetadata в Next.js. Указав список всех доступных языков для данной страницы, Next.js автоматически сгенерирует теги <link rel="alternate" hreflang="..." /> в разделе <head> документа, сигнализируя поисковым системам о взаимосвязи между этими страницами.
Шаги
1. Определите базовый URL вашего сайта
Теги hreflang требуют абсолютных, а не относительных URL. Сохраните канонический базовый URL вашего сайта в конфигурационном файле.
// i18n-config.ts
export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
export const siteBaseUrl = 'https://www.example.com'; // Ваш URL для продакшена
2. Добавьте alternates в generateMetadata
В файле app/[lang]/layout.tsx (чтобы применить ко всем страницам) или в конкретном файле страницы экспортируйте функцию generateMetadata.
// app/[lang]/layout.tsx
import { locales, siteBaseUrl } from '@/i18n-config';
import type { Metadata } from 'next';
type Props = {
params: { lang: string };
children: React.ReactNode;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang } = params;
// Создайте карту языковых альтернатив
const alternatesMap = locales.reduce((acc, locale) => {
acc[locale] = `${siteBaseUrl}/${locale}`;
return acc;
}, {} as Record<string, string>);
return {
alternates: {
canonical: `${siteBaseUrl}/${lang}`,
languages: {
...alternatesMap,
'x-default': `${siteBaseUrl}/${defaultLocale}`,
},
},
};
}
// Остальная часть вашего компонента layout
export default function RootLayout({ children, params }: Props) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
);
}
Этот код генерирует объект languages, который сопоставляет каждую локаль с её абсолютным базовым URL (например, en: 'https://www.example.com/en'). Также он задаёт канонический URL для текущей страницы и URL x-default, который указывает поисковым системам, какую версию показывать пользователям с неопределённым языком.
3. Обработка alternates на вложенных страницах
Для вложенных страниц, таких как /about, необходимо убедиться, что функция метаданных включает полный путь.
// app/[lang]/about/page.tsx
import { locales, siteBaseUrl, defaultLocale } from '@/i18n-config';
import type { Metadata } from 'next';
type Props = {
params: { lang: string };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang } = params;
const path = '/about'; // Путь для этой страницы
// Создание языковых альтернатив
const alternatesMap = locales.reduce((acc, locale) => {
acc[locale] = `${siteBaseUrl}/${locale}${path}`;
return acc;
}, {} as Record<string, string>);
return {
title: 'О нас', // Добавьте переведённый заголовок
alternates: {
canonical: `${siteBaseUrl}/${lang}${path}`,
languages: {
...alternatesMap,
'x-default': `${siteBaseUrl}/${defaultLocale}${path}`,
},
},
};
}
export default function AboutPage() {
return <div>Содержимое страницы "О нас"</div>;
}