链接替代语言版本 (hreflang)
告知搜索引擎您的本地化页面
问题
一个应用程序在 /en/page 和 /fr/page 提供了相同的内容。搜索引擎将这些视为两个独立的、相互竞争的页面。如果没有机制将它们链接起来,搜索排名会被分散,法国的用户可能会在搜索结果中看到英文页面,而不是法文页面。
解决方案
在 Next.js 的 generateMetadata 函数中使用 alternates 属性。通过为给定页面提供所有可用语言的列表,Next.js 会自动在文档的 <head> 中生成 <link rel="alternate" hreflang="..." /> 标签,向搜索引擎表明这些页面之间的关系。
步骤
1. 定义站点的基础 URL
hreflang 标签需要绝对 URL,而不是相对 URL。将站点的规范基础 URL 存储在配置文件中。
// i18n-config.ts
export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
export const siteBaseUrl = 'https://www.example.com'; // 你的生产环境 URL
2. 在 generateMetadata 中添加 alternates
在你的 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}`,
},
},
};
}
// 其余的布局组件
export default function RootLayout({ children, params }: Props) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
);
}
此代码生成一个 languages 对象,将每个语言环境映射到其绝对基础 URL(例如,en: 'https://www.example.com/en')。它还为当前页面设置了一个 canonical URL 和一个 x-default URL,后者告诉搜索引擎在未指定语言的情况下显示哪个版本。
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>;
}