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を受け取り、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エクスポートによって作成されたすべてのメタタグをレンダリングし、ドキュメントの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コンポーネントは、ステップ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: "About Us" }, ...hreflangLinks];
}

これにより、/en/about/fr/about/de/aboutのすべてが、同じ基礎コンテンツに対する正しい言語固有のURLを指すhreflangリンクを生成します。