如何在 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}`,
  }));
}

此函数接收当前的路径名、支持的语言列表以及您网站的基础 URL,然后返回一个链接描述符数组,供元函数渲染。

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: "关于我们" },
    { name: "description", content: "了解我们的公司" },
    ...hreflangLinks,
  ];
}

meta 函数返回一个描述符数组,其中可以包括 tagName 设置为 "link" 的对象。React Router 会将这些渲染为文档头部的 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 链接标签。

5. 为带有语言前缀的路由调整路径名

如果您的路由在路径中包含语言代码(例如 /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: "关于我们" }, ...hreflangLinks];
}

这样可以确保 /en/about/fr/about/de/about 都生成指向相同内容的正确语言特定 URL 的 hreflang 链接。