Next.js App Router と next-intl を使用した多言語対応アプリケーションで、リロードなしの言語切り替えを実装する方法をまとめます。
環境
- Next.js 16 (App Router)
- next-intl
- TypeScript
設定概要
localePrefix: ‘as-needed’ とは
next-intl の localePrefix: 'as-needed' 設定を使用すると、デフォルト言語ではURLにプレフィックスが付かず、その他の言語のみプレフィックスが付きます。
例(デフォルト言語が日本語の場合):
- 日本語:
/,/gallery,/viewer - 英語:
/en,/en/gallery,/en/viewer
実装手順
1. Middleware設定(重要)
next-intl は middleware を使用してロケールのルーティングを処理します。静的ファイルが middleware によってリダイレクトされないよう、matcher の設定が重要です。
注意 : .*\\..* は「ドットを含むパス」を除外します。これにより /og-image.png などの静的ファイルがmiddlewareをスキップし、正常にアクセスできるようになります。
2. ルーティング設定
3. ナビゲーションヘルパーの作成
next-intl が提供する createNavigation を使用して、ロケール対応のナビゲーションヘルパーを作成します。
4. 言語切り替えコンポーネント
ポイント解説
next-intl のナビゲーションを使う理由
next/navigation ではなく next-intl/navigation から useRouter と usePathname をインポートする理由:
- ロケール対応のパス :
usePathname()はロケールプレフィックスを除いた純粋なパスを返す - locale オプション :
router.replace()やrouter.push()に{ locale: 'en' }オプションを渡せる - リロードなし遷移 : クライアントサイドナビゲーションでスムーズに言語切り替え
クエリパラメータの維持
言語切り替え時に ?videoId=xxx&gpxUrl=yyy などのクエリパラメータを維持するために、useSearchParams を使用します。
router.replace vs router.push
router.replace: ブラウザ履歴に新しいエントリを追加しない(推奨)router.push: ブラウザ履歴に新しいエントリを追加
言語切り替えでは replace を使うことで、戻るボタンで前の言語に戻ることを防ぎます。
失敗パターンと解決策
パターン1: window.location.href を使用
動作はするが、ページ全体がリロードされるためUXが悪い。
パターン2: next/navigation の useRouter を使用
手動でパスを構築する必要があり、localePrefix: 'as-needed' との相性が悪い。
パターン3: パスの手動構築
localePrefix の設定によって挙動が変わるため、保守が難しい。
まとめ
next-intl で言語切り替えを実装する際は、以下のポイントを押さえましょう:
createNavigation(routing)でナビゲーションヘルパーを作成useRouter,usePathnameはnext-intl/navigationからインポートrouter.replace(path, { locale })でロケールを指定useSearchParamsでクエリパラメータを維持