如何在 React Router v7 中关联不同语言版本
为搜索引擎链接语言版本
问题
当一个网站以多种语言提供相同内容时,搜索引擎会面临挑战。如果没有明确的信号,搜索引擎会将每个语言版本视为独立且无关的页面。例如,法语用户用法语搜索时,可能会看到英文页面的排名高于法文页面,即使两者都存在。同样,英文用户也可能会进入德语页面。这是因为搜索引擎无法自动判断 /en/about 和 /fr/about 是彼此的翻译版本,而不是内容重复的竞争页面。
这种混淆会导致不同语言版本之间的排名权重被分散,用户体验也会下降。搜索引擎需要明确的元数据来识别哪些页面是同一内容的语言替代版本,从而根据用户的语言偏好和地理位置,提供合适的页面。
解决方案
为每个页面添加 hreflang 链接标签,列出该内容所有可用的语言版本。这些标签通过 rel="alternate" 属性,向搜索引擎表明这些链接页面是翻译版本,而非重复内容。每个标签都指定了语言代码,并指向该语言版本的 URL。
通过在页面元数据中声明这些关系,可以帮助搜索引擎理解网站结构,并为每位用户提供正确的语言版本。这不仅提升了搜索结果的相关性,还能避免因内容重复而受到惩罚。
步骤
1. 创建辅助函数生成 hreflang 链接
hreflang 属性使用 ISO 639-1 语言代码,可选地加上 ISO 3166-1 Alpha 2 区域代码。请编写一个工具,用于为页面的所有语言版本生成链接描述。
type HreflangLink = {
tagName: "link";
rel: "alternate";
hrefLang: string;
href: string;
};
export function buildHreflangLinks(
pathname: string,
locales: string[],
baseUrl: string,
): HreflangLink[] {
return locales.map((locale) => ({
tagName: "link",
rel: "alternate",
hrefLang: locale,
href: `${baseUrl}/${locale}${pathname}`,
}));
}
此函数接收当前 pathname、支持的语言列表以及你的网站基础 URL,然后返回一个链接描述符数组,供 meta 函数渲染。
2. 添加 x-default 备用链接
x-default hreflang 值用于指示默认页面,当没有其他页面更合适且不针对特定语言或地区时使用。添加此项可为你不支持语言的用户提供指引。
export function buildHreflangLinks(
pathname: string,
locales: string[],
baseUrl: string,
defaultLocale: string,
): HreflangLink[] {
const links = locales.map((locale) => ({
tagName: "link",
rel: "alternate",
hrefLang: locale,
href: `${baseUrl}/${locale}${pathname}`,
}));
links.push({
tagName: "link",
rel: "alternate",
hrefLang: "x-default",
href: `${baseUrl}/${defaultLocale}${pathname}`,
});
return links;
}
x-default 链接通常指向你的主要语言版本,并作为那些语言偏好与任何特定语言版本都不匹配用户的备用页面。
3. 从 meta 函数导出 hreflang 链接
meta 函数可以根据数据设置 link 标签。使用它为每个路由返回 hreflang 链接。
import type { Route } from "./+types/about";
import { buildHreflangLinks } from "~/utils/hreflang";
const SUPPORTED_LOCALES = ["en", "fr", "de", "es"];
const BASE_URL = "https://example.com";
const DEFAULT_LOCALE = "en";
export function meta({ location }: Route.MetaArgs) {
const hreflangLinks = buildHreflangLinks(
location.pathname,
SUPPORTED_LOCALES,
BASE_URL,
DEFAULT_LOCALE,
);
return [
{ title: "About Us" },
{ name: "description", content: "Learn about our company" },
...hreflangLinks,
];
}
meta 函数返回一个描述符数组,其中可以包含 tagName 设置为 "link" 的对象。React Router 会将这些渲染为文档 head 中的 link 元素。
4. 确保 Meta 组件在你的根布局中
Meta 组件会渲染由路由模块的 meta 导出创建的所有 meta 标签,且应放在文档的 head 内。请确认你的根布局已包含该组件。
import { Links, Meta, Outlet, Scripts } from "react-router";
export default function Root() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
Meta 组件会聚合并渲染所有匹配路由的 meta 描述符,包括你在第 3 步定义的 hreflang link 标签。
5. 适配带有语言前缀的路由 pathname
如果你的路由路径中包含语言前缀(如 /en/about),在构建 hreflang 链接前应先去除前缀,以确保所有语言版本都指向同一逻辑页面。
export function meta({ location }: Route.MetaArgs) {
const pathWithoutLocale = location.pathname.replace(/^\/[a-z]{2}(\/|$)/, "/");
const hreflangLinks = buildHreflangLinks(
pathWithoutLocale,
SUPPORTED_LOCALES,
BASE_URL,
DEFAULT_LOCALE,
);
return [{ title: "About Us" }, ...hreflangLinks];
}
这样可以确保 /en/about、/fr/about 和 /de/about 都会生成指向相同内容的正确语言版本 URL 的 hreflang 链接。