Next.js (Pages Router) v16でセッション間の言語選択を記憶する方法

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

問題

ユーザーが明示的に言語を選択した場合、その選択は彼らの好みを反映しており、ブラウザヘッダーやジオロケーションからの自動検出よりも優先されるべきです。永続化がない場合、この好みはブラウザを閉じたりセッションが終了したりすると失われます。次回の訪問時には、アプリケーションは新たに開始され、ユーザーは再度言語を選択することを強いられます。この繰り返しは、アプリケーションがユーザーの好みを尊重していないことを示し、摩擦を生み出し、信頼を損ないます。

課題は2つあります。選択の瞬間にユーザーの明示的な選択を捕捉することと、自動検出ロジックが実行される前に、その後の訪問時にその選択を取得することです。保存された好みがリクエストライフサイクルの早い段階でチェックされない場合、ユーザーは明示的な選択ではなくブラウザ設定に基づいてリダイレクトされる可能性があり、そもそも選択を行った価値が損なわれます。

解決策

ユーザーが明示的に言語を選択したときに、その言語選択を永続的なCookieに保存します。アプリケーションのルートへの今後の訪問時には、ブラウザベースのロケール検出にフォールバックする前に、このCookieをチェックします。有効な保存されたロケールが見つかった場合、ユーザーをそのロケールのルートパスにリダイレクトし、彼らの好みが即座に尊重されるようにします。

このアプローチは、明示的なユーザー選択と自動検出を分離します。Cookieは、ブラウザの再起動を超えて存続し、Accept-Languageヘッダーのような一時的なシグナルよりも優先される、意図の永続的なシグナルとして機能します。初期リクエスト中にサーバー側でCookieをチェックすることで、ページがレンダリングされる前にリダイレクトが発生し、シームレスな体験を提供します。

手順

1. クライアント側でロケール設定Cookieを設定するヘルパーを作成する

ユーザーが言語を選択したら、セッション間で保持されるCookieにその選択を保存します。

export function setLocalePreference(locale: string) {
  const maxAge = 60 * 60 * 24 * 365;
  document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=${maxAge}; SameSite=Lax`;
}

この関数は、選択されたロケールを含むNEXT_LOCALEという名前のCookieを1年間有効で書き込みます。path=/により、アプリケーション全体で利用可能になり、SameSite=Laxは、トップレベルナビゲーションでCookieを送信できるようにしながら、適切なCSRF保護を提供します。

2. ユーザーが言語を選択したときにヘルパーを呼び出す

言語切り替えコンポーネントにヘルパーを統合し、選択時に設定がすぐに保存されるようにします。

import { useRouter } from "next/router";
import { setLocalePreference } from "@/lib/locale";

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locales, locale: currentLocale } = router;

  const handleLocaleChange = (newLocale: string) => {
    setLocalePreference(newLocale);
    router.push(router.pathname, router.asPath, { locale: newLocale });
  };

  return (
    <select
      value={currentLocale}
      onChange={(e) => handleLocaleChange(e.target.value)}
    >
      {locales?.map((loc) => (
        <option key={loc} value={loc}>
          {loc.toUpperCase()}
        </option>
      ))}
    </select>
  );
}

ユーザーが選択を変更すると、Cookieが設定され、ルーターは新しいロケールで同じページに移動します。Cookieは以降のすべてのリクエストで利用可能になります。

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

ルートページのgetServerSidePropsで、Cookieを読み取り、存在し有効な場合は保存されたロケールにリダイレクトします。

import { GetServerSideProps } from "next";

export const getServerSideProps: GetServerSideProps = async (context) => {
  const storedLocale = context.req.cookies.NEXT_LOCALE;
  const { locales, defaultLocale } = context;

  if (
    storedLocale &&
    locales?.includes(storedLocale) &&
    storedLocale !== defaultLocale
  ) {
    return {
      redirect: {
        destination: `/${storedLocale}`,
        permanent: false,
      },
    };
  }

  return {
    redirect: {
      destination: `/${defaultLocale}`,
      permanent: false,
    },
  };
};

export default function RootPage() {
  return null;
}

これは、NEXT_LOCALE Cookieが存在し、アプリケーションの設定済みリストから有効なロケールが含まれているかを確認します。保存されたロケールがデフォルトでない場合、ユーザーはそのロケールのルートにリダイレクトされます。それ以外の場合は、デフォルトのロケールにリダイレクトされます。リダイレクトはレンダリング前にサーバー側で行われるため、ユーザーは即座に正しいロケールに到達します。

4. Next.jsでロケールルーティングを設定する

next.config.jsでサポートされるロケールを定義し、リダイレクトロジックが保存された設定を検証できるようにします。

module.exports = {
  i18n: {
    locales: ["en", "fr", "de", "es"],
    defaultLocale: "en",
  },
};

この設定により、Next.jsの組み込みi18nルーティングが有効になり、localesdefaultLocalegetServerSidePropsで利用可能になります。ルートページのロジックは、これらの値を使用して保存されたCookieを検証し、正しいリダイレクト先を構築します。