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ファンクションはデータに基づいてリンクタグを設定できます。これを使用して各ルートの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はこれらをドキュメントヘッドのlink要素としてレンダリングします。

4. ルートレイアウトにMetaコンポーネントが含まれていることを確認する

Metaコンポーネントは、ルートモジュールのmetaエクスポートによって作成されたすべてのメタタグをレンダリングし、ドキュメントのhead内に配置する必要があります。ルートレイアウトにMetaコンポーネントが含まれていることを確認してください。

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リンクを生成します。