Связывание альтернативных языковых версий (hreflang)
Как сообщить поисковикам о локализованных страницах
Проблема
У приложения есть идентичный контент по адресам /en/page и /fr/page. Поисковые системы воспринимают их как две отдельные, конкурирующие страницы. Без механизма связывания их позиции в поиске разделяются, и пользователи из Франции могут увидеть английскую страницу в результатах поиска вместо французской.
Решение
Используйте свойство alternates внутри функции Next.js generateMetadata. Указав список всех доступных языков для страницы, 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'; // Your production 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;
// Create language alternates
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}`,
},
},
};
}
// Rest of your layout component
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 canonical для текущей страницы и 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'; // The path for this page
// Create language alternates
const alternatesMap = locales.reduce((acc, locale) => {
acc[locale] = `${siteBaseUrl}/${locale}${path}`;
return acc;
}, {} as Record<string, string>);
return {
title: 'About Us', // Add your translated title
alternates: {
canonical: `${siteBaseUrl}/${lang}${path}`,
languages: {
...alternatesMap,
'x-default': `${siteBaseUrl}/${defaultLocale}${path}`,
},
},
};
}
export default function AboutPage() {
return <div>About page content</div>;
}