サポートされていない言語コードの処理

問題

アプリケーションはURL パス(例:/en//fr/)を使用して言語を判断しますが、ユーザーは手動で/xx/aboutのような任意の値を入力できます。この値がサポートされている言語と一致しない場合、アプリケーションがクラッシュしたり、一般的なエラーを表示したり、翻訳されていないコンテンツを表示したりして、ユーザーを有効な体験に戻すガイダンスができません。

解決策

すべての受信リクエストを傍受するミドルウェアを使用します。このミドルウェアはURLから言語コードを取得し、サポートされている言語の確定リストと照合します。コードがサポートされていない場合、リクエストはアプリケーションロジックに到達する前に「Not Found」ページに書き換えられます。

手順

1. サポートする言語を定義する

有効な言語コード(ロケール)のリストを保存する中央設定ファイルを作成します。これにより、ミドルウェアやアプリの他の部分でリストを再利用できます。

// i18n.config.ts
export const locales = ['en', 'es', 'fr'];

2. ミドルウェアファイルを作成する

プロジェクトのルート(またはsrc/ディレクトリ)にmiddleware.tsという新しいファイルを作成します。Next.jsはこのファイルを自動的に検出し、リクエスト時に実行します。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  // ロジックは次のステップで追加します
}

3. 検証ロジックを追加する

middleware関数内で、リクエストからpathnameを取得します。パスの最初のセグメント(例:/en/abouten)をチェックし、それが有効でサポートされている言語かどうかを確認する必要があります。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. ルートパスは別途処理する
  // (これは次のレシピ「ユーザーの優先言語の検出」で処理されます)。
  // 今のところ、そのまま通過させます。
  if (pathname === '/') {
    return NextResponse.next();
  }

  // 2. パスから言語コードを抽出する
  const langCode = pathname.split('/')[1];

  // 3. 言語コードがリストにあるかチェックする
  if (locales.includes(langCode)) {
    // 言語が有効なので、リクエストされたページに進む
    return NextResponse.next();
  }

  // 4. 言語が有効でない場合、404ページに書き換える
  // これにより無効なURLはブラウザバーに残ります
  const url = request.nextUrl.clone();
  url.pathname = `/404`; // app/404.tsxファイルがあることを前提としています
  return NextResponse.rewrite(url);
}

このロジックはすべてのリクエストをチェックします。URLが/fr/aboutの場合、langCodefrとなり、locales内に見つかるため、リクエストは続行されます。URLが/xx/aboutの場合、langCodexxとなり、見つからないため、アプリケーションが無効なリクエストを処理しようとする前に、ユーザーには404ページが表示されます。

4. ミドルウェアマッチャーの設定

ミドルウェアをより効率的にするために、どのパスで実行するかを指定する必要があります。ページリクエストでは実行させたいですが、静的ファイルやAPIルートではスキップさせたいです。

middleware.tsファイルの下部にconfigオブジェクトを追加してください。

// middleware.ts

// ... (上記のミドルウェア関数)

export const config = {
  matcher: [
    // 以下で始まるパスはすべてスキップ:
    // - api (APIルート)
    // - _next/static (静的ファイル)
    // - _next/image (画像最適化ファイル)
    // - favicon.ico (ファビコンファイル)
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

この正規表現は、静的アセットやAPIコール用の典型的なパスを除くすべてのパスでミドルウェアを実行するよう指示します。これにより、すべての画像、フォント、またはデータリクエストに対する不要な検証を防ぎます。