前提知識

ミドルウェアとは?初心者向けガイド

ミドルウェアの基本、SaaSで使われる理由、Next.jsのi18nとrequest IDを扱うミドルウェアの仕組み、そして安全なカスタマイズ方法を解説します。

一言でいうと

ミドルウェアは、リクエストと最終的なハンドラの間で動くコードです。ページやAPIが動く前に、検査・リダイレクト・ヘッダー追加ができます。


Middleware 101(初心者視点)

リクエストを道路を走る車だとすると、ミドルウェアは検問所です。すべてのリクエストが通過し、そこで内容を確認したり判断したり、必要ならレスポンスを変えたり(早めに止めたり)できます。

Webアプリでの代表的な用途:

  • 認証ゲート: サインイン済みか確認し、未ログインならリダイレクト。
  • 国際化(i18n): URLや設定に応じて適切なロケールへ誘導。
  • ロギングとトレーシング: request_id を付与してログを相関。
  • フィーチャーフラグ / A/Bテスト: 早い段階でユーザーを振り分け。
  • セキュリティとレート制限: 悪質なトラフィックを遮断、ヘッダーを強制。

Next.jsでは middleware.ts という特別なファイルに書き、エッジで(非常に早く)実行されます。リダイレクト/リライト、ヘッダーの読み書き、レスポンスのショートサーキットを、ハンドラの前に行えます。


どんなときに役立つか

ミドルウェアは横断的な関心ごと(多くのルートに共通する処理)を集約できます。結果として:

  • ルールの一元化: authやロケールの抜け漏れが減る。
  • 観測性の統一: すべてのリクエストに request_id を付けられる。
  • 早い判断: エッジでリダイレクトや拒否を行い、サーバー負荷を減らす。
  • ハンドラがすっきり: ビジネスロジックに集中できる。

SaaSで特に効くケース:

  • /:locale/... を維持したい多言語アプリ。
  • リクエスト単位の相関IDが必要なログ/トレース。
  • 有料機能や管理エリアのゲート(Cookieは慎重に扱う)。
  • A/B、地域ルーティング、ヘッダー正規化。

現在のミドルウェアの役割

src/middleware.ts に、next-intl のi18nルーティングと軽量なrequest ID付与を組み合わせた安全なデフォルトを用意しています。

主な挙動:

  • ロケールルーティング: next-intl に委譲し、src/i18n/routing.tssrc/i18n/locale.ts を参照します。/en/.../fr/... を一貫させ、必要なら自動検出も可能。
  • Request ID: すべてのレスポンスに x-request-id を付与。すでにあれば再利用し、なければUUIDを生成。
  • 保守的な matcher: ローカライズされたページと一般ルートのみ対象。api_next、アセット、admin は除外。

現在のコード(要点のみ):

// src/middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';
import { routing } from '@/i18n/routing';

const intlMiddleware = createMiddleware(routing);

export default function middleware(request: NextRequest) {
  const existing = request.headers.get('x-request-id');
  const requestId = existing || crypto.randomUUID();

  const res = intlMiddleware(request);
  // 可視化のみ。リクエストのcookie/headersは変更しない。
  res.headers.set('x-request-id', requestId);
  return res;
}

export const config = {
  matcher: [
    '/',
    '/(en|en-US|zh|zh-CN|zh-TW|zh-HK|zh-MO|ja|ko|ru|fr|de|ar|es|it)/:path*',
    '/((?!api|_next|_vercel|admin|.*\\..*).*)',
  ],
};

ロケール設定:

// src/i18n/routing.ts
import { defaultLocale, localeDetection, localePrefix, locales } from './locale';
import { defineRouting } from 'next-intl/routing';

export const routing = defineRouting({ locales, defaultLocale, localePrefix, localeDetection });

ロケール定義(追加/削除する場所):

// src/i18n/locale.ts
export const locales = ['en', 'zh', 'es', 'fr', 'ja'];
export const defaultLocale = 'en';
export const localePrefix = 'always';
export const localeDetection = process.env.NEXT_PUBLIC_LOCALE_DETECTION === 'true';

注意点:

  • Better Auth を安定させるため、ミドルウェアでリクエストのCookieやヘッダーは変更しません。レスポンスヘッダーのみ付与します。
  • matcher は api_next、アセット、admin を除外しています。必要なら調整してください。

安全にカスタマイズする方法

まず、その処理がミドルウェア向きか(横断的で早期判断が必要か)を確認しましょう。適切なら次のパターンが使えます。

カスタムヘッダーの追加

export default function middleware(request: NextRequest) {
  const res = intlMiddleware(request);
  res.headers.set('x-feature-flag', 'on');
  return res;
}

リダイレクト/リライト

import { NextResponse } from 'next/server';

export default function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  if (pathname === '/old') {
    return NextResponse.redirect(new URL('/new', request.url));
  }
  return intlMiddleware(request);
}

matcher で対象を調整

export const config = {
  matcher: [
    '/',
    // admin を対象に戻すには negative lookahead から外すか
    // 明示的にマッチさせる:
    '/admin/:path*',
    '/(en|fr|ja|zh|es)/:path*',
    '/((?!api|_next|_vercel|.*\\..*).*)',
  ],
};

request ID をハンドラで使う

// Node のルートハンドラで
import { headers } from 'next/headers';

export async function GET() {
  const h = headers();
  const request_id = h.get('x-request-id');
  // ログやDBトレースに付与
  return new Response('ok');
}

国際化の調整

  • NEXT_PUBLIC_LOCALE_DETECTIONtrue にして自動検出を有効化。
  • src/i18n/locale.ts でロケールの追加/削除。
  • 新しいロケール追加時は content/docsmessages/src/i18n を同期。

認証などのセンシティブな処理

  • ミドルウェアでの認証ゲートはCookieの変更を避け、リダイレクトや読み取りのみで行う。
  • 保護APIはハンドラ側でも必ず検証する(ミドルウェアは補助でしかない)。

ミドルウェアを使わない方が良い場面

  • ルート固有のロジックで、横断的ではない。
  • 重い非同期処理(DBアクセスなど)。
  • 大きなリクエストボディの変換(ミドルウェアでは読めない)。

ミドルウェアは小さく、速く、集中させるのが基本です。複雑化したら、サービス関数に移してハンドラから呼び出しましょう。


TL;DR

  • ミドルウェアは早く・中央で動くので、i18n、ヘッダー、リダイレクト、軽いガードに最適。
  • デフォルトのミドルウェアはロケールルーティングと x-request-id を提供。
  • config.matcher、レスポンスヘッダー、安全なリダイレクトでカスタマイズ。認証Cookieは触らない。
  • 小さく、速く、ロケール間で一貫させる。