如何在 TanStack Start v1 中链接替代语言版本
为搜索引擎链接语言替代版本
问题
当一个网站提供多种语言的相同内容时,搜索引擎默认将每种语言版本视为单独的页面。如果没有明确的信号将这些版本连接起来,搜索引擎无法理解 /en/about 和 /fr/about 是相同内容的翻译版本,而不是相互竞争的重复内容。这种分裂会将排名权重分散到不同语言版本中,并导致服务问题:例如,一个讲法语的用户可能会在搜索结果中看到英语版本的排名更高,即使存在法语翻译版本。搜索引擎需要明确的元数据来理解语言版本之间的关系,并根据用户的语言偏好和位置提供适当的版本。
解决方案
在文档的头部添加 hreflang 链接元素,声明每个页面的所有可用语言版本。这些链接告诉搜索引擎哪些 URL 包含相同内容的不同语言版本,从而允许它们合并排名信号并向用户提供正确的版本。每个页面列出其所有语言替代版本,包括对自身的引用,创建一个双向关系,搜索引擎可以利用这些关系来理解翻译结构。这些元数据是通过框架的头部管理系统按路由添加的,该系统可以访问构建所有语言版本 URL 所需的当前路由参数。
步骤
1. 创建一个辅助函数以构建语言替代 URL
路由器的 buildLocation 方法通过路由参数构建完整的 URL,您可以使用它为每种语言版本生成 URL。
import { AnyRouter } from "@tanstack/react-router";
export function buildLanguageAlternates(
router: AnyRouter,
currentPath: string,
currentLang: string,
availableLanguages: string[],
) {
return availableLanguages.map((lang) => {
const location = router.buildLocation({
to: currentPath,
params: { lang },
});
return {
lang,
href: `${location.pathname}${location.search}${location.hash}`,
};
});
}
此函数接收当前路由路径,并通过将每种语言代码替换到路由参数中生成替代 URL。
2. 定义可用语言
创建一个配置文件,列出您的应用程序支持的所有语言。
export const AVAILABLE_LANGUAGES = ["en", "fr", "de", "es"];
export const DEFAULT_LANGUAGE = "en";
此集中列表将用于为每个页面生成 hreflang 链接。
3. 在路由的 head 函数中添加 hreflang 链接
head 函数接收上下文,包括 matches、params 和 loaderData,这些提供了对当前语言参数和路由实例的访问。
import { createFileRoute } from "@tanstack/react-router";
import { buildLanguageAlternates, AVAILABLE_LANGUAGES } from "../i18n-config";
export const Route = createFileRoute("/$lang/about")({
head: ({ params }) => {
const alternates = buildLanguageAlternates(
Route.router,
"/$lang/about",
params.lang,
AVAILABLE_LANGUAGES,
);
return {
links: alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
};
},
component: AboutPage,
});
function AboutPage() {
return <div>关于页面内容</div>;
}
hreflang 属性使用 ISO 639-1 语言代码,每个链接指向同一页面的不同语言版本。
4. 为回退行为添加 x-default hreflang
x-default hreflang 属性指示当没有语言匹配时的默认页面。
export const Route = createFileRoute("/$lang/about")({
head: ({ params }) => {
const alternates = buildLanguageAlternates(
Route.router,
"/$lang/about",
params.lang,
AVAILABLE_LANGUAGES,
);
const defaultUrl = alternates.find((alt) => alt.lang === "en");
return {
links: [
...alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
{
rel: "alternate",
hreflang: "x-default",
href: defaultUrl?.href || alternates[0].href,
},
],
};
},
component: AboutPage,
});
x-default 链接为用户提供了一个回退 URL,当用户的语言偏好与任何声明的替代语言不匹配时使用。
5. 应用于带有参数的动态路由
对于包含语言之外的其他动态段的路由,在构建替代链接时需要包括这些参数。
export const Route = createFileRoute("/$lang/posts/$postId")({
head: ({ params }) => {
const alternates = AVAILABLE_LANGUAGES.map((lang) => {
const location = Route.router.buildLocation({
to: "/$lang/posts/$postId",
params: { lang, postId: params.postId },
});
return {
lang,
href: `${location.pathname}${location.search}${location.hash}`,
};
});
return {
links: [
...alternates.map((alt) => ({
rel: "alternate",
hreflang: alt.lang,
href: alt.href,
})),
{
rel: "alternate",
hreflang: "x-default",
href:
alternates.find((a) => a.lang === "en")?.href || alternates[0].href,
},
],
};
},
});
每个页面必须引用其所有语言版本,包括其自身,以确保搜索引擎能够理解该内容的完整翻译集。
6. 验证 hreflang 的实现
head 函数返回的 link 元素由 HeadContent 组件渲染。检查渲染的 HTML 以确认这些链接出现在文档的 head 中。
<link rel="alternate" hreflang="en" href="/en/about" />
<link rel="alternate" hreflang="fr" href="/fr/about" />
<link rel="alternate" hreflang="de" href="/de/about" />
<link rel="alternate" hreflang="es" href="/es/about" />
<link rel="alternate" hreflang="x-default" href="/en/about" />
页面之间的双向链接确保搜索引擎能够理解本地化版本之间的关系,从而为每位用户提供最合适的版本。