链接其他语言版本(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'; // Your production 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;
// 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 对象,将每个 locale 映射到其绝对基础 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'; // 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>;
}