TanStack Start v1で代替言語版をリンクする方法

検索エンジン向けに言語の代替版をリンクする

問題

ウェブサイトが同じコンテンツを複数の言語で提供する場合、検索エンジンはデフォルトで各言語版を別々のページとして扱います。これらの版を結びつける明示的なシグナルがなければ、検索エンジンは/en/about/fr/aboutが競合する重複ではなく、同じコンテンツの翻訳であることを理解できません。この断片化により、ランキングの権威が言語版全体に分散され、配信の問題が発生します。フランス語を話すユーザーは、フランス語の翻訳が存在するにもかかわらず、検索結果で英語版が上位にランク付けされる可能性があります。検索エンジンは、言語バリアント間の関係を理解し、ユーザーの言語設定と場所に基づいて適切な版を配信するために、明示的なメタデータを必要とします。

解決策

各ページの利用可能なすべての言語版を宣言するhreflang link要素をドキュメントのheadに追加します。これらのリンクは、どのURLに異なる言語の同じコンテンツが含まれているかを検索エンジンに伝え、ランキングシグナルを統合してユーザーに正しい版を配信できるようにします。各ページは、自身への参照を含むすべての言語の代替版をリストし、検索エンジンが翻訳構造を理解するために使用する双方向の関係を作成します。このメタデータは、すべての言語バリアントのURLを構築するために必要な現在のルートパラメータにアクセスできる、フレームワークのhead管理システムを使用してルートごとに追加されます。

手順

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>About page content</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. パラメータを持つ動的ルートに適用する

言語以外の追加の動的セグメントを持つルートの場合、代替URLを構築する際にそれらのパラメータを含めます。

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関数はHeadContentコンポーネントによってレンダリングされるlink要素を返します。レンダリングされた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" />

ページ間の双方向リンクにより、検索エンジンがローカライズされたバージョン間の関係を理解し、各ユーザーに最も適切なバージョンを提供できるようになります。