Next.js の App Router には、リクエストがページや API に届く前に処理を差し込める Middleware(ミドルウェア)という仕組みがあります。プロジェクト直下に置いた middleware.ts というファイルが、ページの表示や API の実行より先に呼ばれるため、ログインしていない人をログインページへ飛ばしたり、リクエストの内容によって別のパスへ書き換えたりといった「入口での共通処理」をまとめて書けます。この記事では、middleware.ts の基本形から、matcher で対象パスを絞る方法、リダイレクトや認証チェックといった代表的な使い道、そして Edge Runtime で動くことによる注意点まで、初心者〜中級者向けにコード付きで解説します。
目次
Middleware とは何か
Middleware は、リクエストがルーティング(どのページ・API を表示するかの振り分け)で処理される前に実行される関数です。ユーザーがあるページにアクセスすると、まず Middleware が呼ばれ、そこでリクエストの内容を見て「そのまま通す」「別のパスへリダイレクトする」「内部的に別のパスへ書き換える」「ヘッダーを足して通す」といった判断ができます。ページごとに同じチェックを書く代わりに、入口でまとめて処理できるのが利点です。
Middleware を有効にするには、プロジェクトのルート直下(app や pages と同じ階層)に middleware.ts というファイルを置きます。src ディレクトリを使っている場合は src/middleware.ts に置きます。ファイル名と配置は決まっていて、プロジェクトに 1 つだけです。複数のファイルに分けることはできません。
基本の書き方
最小の形は、middleware という名前の関数を export するだけです。引数として、リクエストの情報を持つ NextRequest オブジェクトが渡されます。何もせずそのまま通したいときは、NextResponse.next() を返します。これは「このリクエストを次の処理(本来のページや API)へ進める」という意味です。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// リクエストがページや API に届く前に実行される
export function middleware(request: NextRequest) {
// request からパスやクッキーなどを参照できる
console.log("アクセスされたパス:", request.nextUrl.pathname);
// 何もせず、そのまま本来の処理へ進める
return NextResponse.next();
}
引数の request は NextRequest 型で、これはブラウザ標準の Request を拡張したものです。request.nextUrl で URL の情報(pathname や searchParams など)、request.cookies でクッキー、request.headers でリクエストヘッダーを参照できます。戻り値として返す NextResponse は、レスポンスを表すオブジェクトで、next() のほかに次で紹介する redirect() や rewrite() といったメソッドを持っています。関数は同期・非同期どちらでも書けるため、async function middleware(...) として await を使うこともできます。
matcher で対象パスを絞る
上のコードのままでは、Middleware がすべてのリクエストに対して実行されます。画像や CSS などの静的ファイルまで通ると無駄が多いため、通常は「どのパスで動かすか」を指定します。そのために、config という名前のオブジェクトを export し、その中の matcher に対象パスを書きます。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
return NextResponse.next();
}
// /dashboard 以下のパスだけで middleware を実行する
export const config = {
matcher: "/dashboard/:path*",
};
matcher には文字列を 1 つ書くことも、配列で複数書くこともできます。パスの書き方は path-to-regexp という記法に基づいており、:path* のように書くと「その配下すべて」を表します。代表的な書き方と対象を、次の表にまとめます。
| matcher の値 | 対象になるパス |
|---|---|
"/about" | /about のみ(完全一致) |
"/dashboard/:path*" | /dashboard とその配下すべて(/dashboard/settings など) |
"/blog/:slug" | /blog/xxx のような 1 階層(/blog/xxx/yyy は含まない) |
["/about", "/dashboard/:path*"] | 複数指定。いずれかに一致すれば実行 |
「静的ファイルや画像を除いた、ほぼすべてのパスで動かしたい」というときは、正規表現の否定先読みを使った書き方がよく使われます。次の例は、api・Next.js の内部ファイル(_next/static、_next/image)・favicon.ico を除いたすべてのパスに一致させるものです。
export const config = {
matcher: [
// api・_next/static・_next/image・favicon.ico 以外のすべてのパス
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};
リダイレクトと書き換え:redirect と rewrite
Middleware の代表的な使い道が、リクエストの行き先を変えることです。NextResponse には、この用途のためのメソッドが 2 つあります。NextResponse.redirect() は、ブラウザの URL 自体を変えて別のページへ移動させます(ユーザーからも URL が変わって見えます)。一方 NextResponse.rewrite() は、URL は変えないまま、内部的に別のパスの内容を表示します(ユーザーには元の URL のまま、中身だけが差し替わります)。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// /old-page にアクセスされたら /new-page へリダイレクト(URL が変わる)
if (request.nextUrl.pathname === "/old-page") {
return NextResponse.redirect(new URL("/new-page", request.url));
}
// /dashboard は URL はそのままに、内部で /hidden の内容を表示(書き換え)
if (request.nextUrl.pathname === "/dashboard") {
return NextResponse.rewrite(new URL("/hidden", request.url));
}
return NextResponse.next();
}
行き先を指定するときは、new URL("/new-page", request.url) のように、第 2 引数へ現在のリクエスト URL を渡して絶対 URLを作るのがポイントです。request.url を基準にすることで、現在アクセスされているドメインを引き継いだ正しい URL になります。
クッキーを見て未ログインならログインページへ
認証(ログイン)チェックは、Middleware がもっとも活躍する場面のひとつです。ログイン状態をクッキーやヘッダーで判定し、未ログインならログインページへリダイレクトする、という処理を入口でまとめて行えます。次の例では、token というクッキーの有無でログイン状態を判定しています。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// token クッキーを取り出す(無ければ undefined)
const token = request.cookies.get("token");
// 未ログインなら /login へリダイレクト
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// ログイン済みならそのまま通す
return NextResponse.next();
}
// /dashboard 以下だけを認証チェックの対象にする
export const config = {
matcher: "/dashboard/:path*",
};
request.cookies.get("token") は、クッキーが存在すれば { name, value } の形のオブジェクトを、存在しなければ undefined を返します。そのため、上のように if (!token) で「クッキーが無い=未ログイン」と判定できます。ヘッダーを見たい場合は request.headers.get("authorization") のように取得します。ここではクッキーの有無だけを見ていますが、実際にはトークンの中身が正しいかまで検証するのが安全です。
リクエストヘッダーを追加して渡す
リクエストをそのまま通しつつ、ヘッダーを付け足して後続のページや API に渡すこともできます。NextResponse.next() にオプションで request.headers を渡すと、変更後のヘッダーで処理を続行できます。次の例では、x-custom-header という独自ヘッダーを追加しています。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// 既存のリクエストヘッダーを複製して、新しいヘッダーを追加する
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-custom-header", "hello");
// 追加したヘッダー付きで、後続の処理へ進める
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
後続のページ側では、追加したヘッダーを読み取って利用できます。レスポンス側にヘッダーを付けたいときは、いったん const response = NextResponse.next() で受け取り、response.headers.set(...) を呼んでから response を返します。
Edge Runtime で動くことによる制約
Middleware は通常の Node.js サーバーではなく、Edge Runtimeという軽量な実行環境で動きます。これはリクエストのたびに素早く起動して処理を返すことを重視した環境で、そのぶん使える機能に制限があります。具体的には、fs(ファイル操作)や net のような Node.js 専用の API は使えません。標準的な Web API(fetch、URL、Headers など)は利用できます。
また Middleware は「すべてのリクエストの入口」で動くため、処理が重いと全ページの表示が遅くなります。データベースへの直接接続や時間のかかる計算は Middleware に置かず、軽い判定(クッキーの確認、パスの分岐など)にとどめるのが基本です。重い処理は、ページ側や Route Handler、Server Actions などに任せます。
動かないときに確認すること
ファイル名と置き場所が正しいか
Middleware がまったく実行されないときに最初に疑うべきは、ファイル名と配置です。ファイル名は必ず middleware.ts(または middleware.js)で、app や pages と同じルート直下に置く必要があります。src ディレクトリ構成なら src/middleware.ts です。app の中など、別の階層に置いても認識されません。また、プロジェクトに 1 つだけという決まりがあるので、複数作ることはできません。
matcher の対象から外れていないか
特定のパスだけ Middleware が動かないときは、config.matcher の指定を見直します。たとえば "/dashboard" とだけ書いていると /dashboard/settings は対象外です。配下も含めたいなら "/dashboard/:path*" のように書きます。逆に、意図しないパスでも動いてしまうときは、matcher が広すぎないか(matcher を書き忘れて全パス対象になっていないか)を確認します。
Node.js 専用 API を使っていないか
実行時にエラーになる場合、Edge Runtime では動かない Node.js 専用の API やライブラリを使っていないか確認します。ファイル読み書きや、Node.js 環境を前提としたパッケージは Middleware では動きません。こうした処理が必要なら、Middleware では判定だけを行い、実処理はページや Route Handler 側に移します。
まとめ
Middleware は、リクエストがページや API に届く前に共通処理を差し込むための仕組みです。プロジェクトのルート直下(または src 直下)に middleware.ts を 1 つ置き、export function middleware(request: NextRequest) を書いて、NextResponse.next()/redirect()/rewrite() のいずれかを返します。config.matcher で対象パスを絞れば、必要なパスだけで動かせます。クッキーやヘッダーを見て未ログインならログインページへ飛ばす認証チェックや、リクエストヘッダーの追加といった用途に向いています。ただし Edge Runtime で動くため Node.js 専用 API は使えず、全リクエストの入口になる以上、重い処理は避けて軽い判定にとどめるのが安全です。