⛰️

Next.js:middleware.ts に入門する

に公開

こんにちは。クラウドエース第四開発部の富田です。
Next.js の middleware.ts の存在に何となく苦手意識を持っていたので、今回調べたことをまとめてみます。

Next.js Middleware とは何か?

Middleware は、ユーザーからのリクエストがサーバーに届いてから、Next.js がページや API ルートを処理する直前のタイミングで実行されるものです。
このタイミングでリクエストを検査し、必要に応じて内容を書き換えたり、別のページにリダイレクトしたりといった処理を差し込むことができます。

代表的なユースケース

以下は、代表的なユースケースです。

  • 認証・認可:セッション情報やクッキーを確認し、特定のパスや API へのアクセスを制限します。
  • リダイレクト:ユーザーの権限に基づきリダイレクトします。
  • URL の書き換え:ユーザーに表示されるURLは変えずに、内部的に表示するページを書き換えます。A/B テストなどで特に有効です。
  • 多言語対応:リクエストヘッダーからユーザーの優先言語を判別し、適切な言語のページに自動で振り分けます。
  • Cookie の操作:リクエストに応じてCookieを動的に設定・削除します。例えば、特定のキャンペーンページにアクセスしたユーザーにフラグを立てることができます。

API Routes との使い分け

  • Middleware:リクエストヘッダーや Cookie のチェック、リダイレクト/リライトなど、レスポンスボディを返さない前処理。
  • API Routes:データベースへのアクセス、外部APIとの通信など、レスポンスボディを生成する具体的な処理。

Middleware のセットアップと基本構造

セットアップ

Middleware を使うには、プロジェクトのルートディレクトリ、またはsrcディレクトリ直下にmiddleware.tsを作成します。

App Router の場合は以下のように src 直下に配置します。

my-next-app/
├── src/
│   ├── app/
│   └── middleware.ts  //<-- ここに作成!
├── package.json
...

現在の Next.js の仕様では、middleware.ts ファイルはプロジェクトに1つしか置けません。
複数の異なる処理を行いたい場合は、1つの middleware.ts ファイル内で条件分岐( if 文や switch 文)を使って記述する必要があります。

基本的な書き方

記述例

// src/middleware.ts

import { NextRequest, NextResponse } from 'next/server'; 

export function middleware(request: NextRequest) {
  // Middleware のロジック本体を記述する
  console.log('Middlewareが実行されました:', request.nextUrl.pathname);
  return NextResponse.next(); // 以降の処理を継続する
}

// Middlewareを実行するパスを指定
export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

matcher で実行パスを絞る

デフォルトでは、Middleware はプロジェクト内のすべてのリクエスト(ページ、API、画像ファイル、CSSファイルなど)に対して実行されます。
それに対し、config オブジェクトと matcher プロパティを使うことで、Middlewareを実行するパスを限定できます。

上の記述例では、api (API routes), _next/static (static files), _next/image (image optimization files), favicon.ico (favicon file) で始まらないパスを対象に指定しています。

Edge Runtime による制限

Middlewareは、軽量な Edge Runtime 上で実行されます。
そのため非常に高速な動作が期待できます。

高速である一方、Edge Runtime では Node.js のネイティブ API(例:fsモジュールでのファイル読み書き)など、一部の機能が利用できません。
そのため、Middleware 内では、Web Standard API ( fetch, Request, Response など)を中心にコードを記述する必要があります。

実装例

認証とリダイレクト

以下の例は、ログインしていないユーザーがダッシュボードページにアクセスしようとした際に、ログインページへリダイレクトさせるものです。

// src/middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  // クッキーから認証トークンを取得
  const authToken = request.cookies.get('auth-token')?.value;

  // 認証トークンがなく、かつ保護対象のページにアクセスしようとしている場合
  if (!authToken && request.nextUrl.pathname.startsWith('/dashboard')) {
    // ログインページへリダイレクト
    const loginUrl = new URL('/login', request.url);  
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/dashboard/:path*', // /dashboardとその配下の全ページで実行
};

まとめ

Next.js Middleware は、アプリケーションの「門番」として、横断的な関心事を一元的に、かつ効率的に処理するための非常に強力な機能です。
ユースケース対象の処理をページコンポーネントから分離することで、コードはよりクリーンに、アプリケーションはより堅牢で保守しやすくなります。

最初は少しとっつきにくい概念かもしれませんが、一度その便利さを体験すれば、もう Middleware なしの開発は考えられなくなるはずです。
ぜひ、あなたの次のプロジェクトでこの「縁の下の力持ち」を活用してみてください。

Discussion