TanStack Start v1でセッション間の言語選択を記憶する方法

ユーザーの明示的な言語選択を保存する

問題

ユーザーが明示的に言語を選択した場合、その選択は現在のブラウザセッションを超えて持続すべき意図的な設定を表します。永続化がない場合、アプリケーションは訪問のたびにこの設定を忘れてしまい、ユーザーは繰り返し言語を再選択することを強いられます。これにより摩擦が生じ、アプリケーションがユーザーの選択を尊重していないことを示し、全体的なエクスペリエンスが低下し、ユーザーが意図したタスクを完了する前にサイトを離脱する可能性があります。

解決策

ユーザーが明示的に選択した際に、その言語選択を永続的なCookieに保存します。その後の訪問時には、ブラウザヘッダーなどの自動検出方法にフォールバックする前に、この保存された設定を確認します。有効な保存済み言語が見つかった場合、サイトのルートからその言語のパスにユーザーをリダイレクトし、追加の手順なしで優先ロケールに直接アクセスできるようにします。

手順

1. 言語設定を保存するサーバー関数を作成する

サーバー関数を使用すると、アプリケーション内のどこからでも呼び出せるサーバー専用ロジックを定義できます。選択されたロケールをCookieに書き込む関数を定義します。

import { createServerFn } from "@tanstack/react-start";
import { setCookie } from "@tanstack/react-start/server";

const LOCALE_COOKIE = "user_locale";
const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;

export const saveLocalePreference = createServerFn({ method: "POST" })
  .validator((locale: string) => locale)
  .handler(async ({ data }) => {
    setCookie(LOCALE_COOKIE, data, {
      maxAge: COOKIE_MAX_AGE,
      path: "/",
      sameSite: "lax",
    });
    return { success: true };
  });

このサーバー関数は@tanstack/react-start/serversetCookieを使用してロケール設定を保存し、今後のリクエストで利用可能にします。

2. ユーザーが言語を選択したときに保存関数を呼び出す

言語切り替えコンポーネントで、ユーザーが選択した後にサーバー関数を呼び出します。

import { useNavigate } from "@tanstack/react-router";
import { saveLocalePreference } from "./locale-preference";

export function LanguageSwitcher({ currentLocale }: { currentLocale: string }) {
  const navigate = useNavigate();

  const handleLocaleChange = async (newLocale: string) => {
    await saveLocalePreference({ data: newLocale });
    navigate({ to: `/${newLocale}` });
  };

  return (
    <select
      value={currentLocale}
      onChange={(e) => handleLocaleChange(e.target.value)}
    >
      <option value="en">English</option>
      <option value="es">Español</option>
      <option value="fr">Français</option>
    </select>
  );
}

これにより、新しいロケールに移動する前に設定が保存されます。

3. サーバー関数を作成して保存された設定を読み取る

Cookieから保存されたロケールを取得する関数を定義します。

import { createServerFn } from "@tanstack/react-start";
import { getCookie } from "@tanstack/react-start/server";

const LOCALE_COOKIE = "user_locale";

export const getStoredLocale = createServerFn({ method: "GET" }).handler(
  async () => {
    const stored = getCookie(LOCALE_COOKIE);
    return stored || null;
  },
);

@tanstack/react-start/servergetCookie関数は、以前の訪問時に設定されたCookie値を読み取ります。

4. ルートルートで保存された設定を確認する

ルートのbeforeLoadコールバックでredirect関数を使用して、保存された設定が見つかった場合にリダイレクトをトリガーします。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getStoredLocale } from "./locale-preference";

const SUPPORTED_LOCALES = ["en", "es", "fr"];
const DEFAULT_LOCALE = "en";

export const Route = createFileRoute("/")({
  beforeLoad: async () => {
    const stored = await getStoredLocale();

    if (stored && SUPPORTED_LOCALES.includes(stored)) {
      throw redirect({ to: `/${stored}` });
    }

    throw redirect({ to: `/${DEFAULT_LOCALE}` });
  },
});

これは、保存された設定を最初に確認し、有効な場合はそのロケールにリダイレクトし、それ以外の場合はデフォルトにフォールバックします。

5. 保存されたロケールをサポートされている言語に対して検証する

リダイレクトする前に、保存された値が認識されたロケールであることを確認するための検証を追加します。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getStoredLocale } from "./locale-preference";

const SUPPORTED_LOCALES = ["en", "es", "fr", "de", "ja"] as const;

function isValidLocale(value: string | null): value is string {
  return value !== null && SUPPORTED_LOCALES.includes(value as any);
}

export const Route = createFileRoute("/")({
  beforeLoad: async () => {
    const stored = await getStoredLocale();
    const locale = isValidLocale(stored) ? stored : "en";
    throw redirect({ to: `/${locale}` });
  },
});

これにより、無効またはサポートされていないロケールへのリダイレクトを防ぎ、Cookie改ざんや古い値から保護します。