Next.js(ページルーター)v16でセッション間の言語選択を記憶する方法

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

問題

ユーザーが明示的に言語を選択した場合、その選択はブラウザヘッダーや位置情報からの自動検出よりも優先されるべきです。永続化がなければ、この設定はブラウザを閉じたりセッションが終了すると失われます。次回訪問時、アプリケーションは初期状態から始まり、ユーザーは再度言語を選択する必要があります。この繰り返しは、アプリケーションがユーザーの設定を尊重していないことを示し、摩擦を生み出し、信頼を損なうことになります。

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

解決策

ユーザーが明示的に言語を選択した際に、その言語選択を永続的なクッキーに保存します。アプリケーションのルートに再訪問する際、ブラウザベースのロケール検出に頼る前に、このクッキーをチェックします。有効な保存されたロケールが見つかった場合、ユーザーをそのロケールのルートパスにリダイレクトし、彼らの設定が即座に尊重されるようにします。

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

手順

1. クライアントサイドでロケール設定クッキーを設定するヘルパーを作成する

ユーザーが言語を選択した際、セッション間で保持されるクッキーに選択内容を保存します。

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

この関数はNEXT_LOCALEという名前のクッキーを書き込み、選択されたロケールを1年間有効にします。path=/はアプリケーション全体でクッキーが利用可能であることを保証し、SameSite=Laxはトップレベルのナビゲーションでクッキーが送信されることを許可しながら、適切な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>
  );
}

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

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

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

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

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

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

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

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