Next.js の middleware が実行されない
middleware.ts はプロジェクトルートに 1 ファイル限定で、matcher の設定と Edge runtime 制約に従う必要がある。
配置と matcher がずれると静かにスキップされ、export 名を間違えるとリクエストがすべて 500 になる。
公開: 更新:
要約
middleware.ts が動かないときは「ファイルの場所」「matcher」「export 名」「ランタイム制約」を順に疑う。
公式の middleware.js(v15)(新しいタブで開く) のとおり、middleware は プロジェクトルートに 1 ファイルしか置けず、複数置いても認識されない。
なお Next.js 16.0.0 でこのファイル規約は非推奨になり proxy(新しいタブで開く) へ改名された。
16 以降は同じ位置に proxy.ts を置く。
実行例
Next.js 15.5.26 で、レスポンスに x-middleware: hit を付けるだけの middleware を app/ の中に置くと、next build の一覧に ƒ Middleware が出ずヘッダも付かなかったが、プロジェクトルートへ移すと両方とも現れた。
matcher を /api/:path* だけにするとトップページには付かず、API と静的アセットを除く matcher に直すとトップには付いて /api/hello には付かなくなり、関数名を myMiddleware に変えるとビルドは通るのにトップが 500 Internal Server Error になった。
$ npx next --version
Next.js v15.5.26$ npx next build
▲ Next.js 15.5.26
Creating an optimized production build ...
✓ Compiled successfully in 2.1s
Linting and checking validity of types ...
Collecting page data ...
Generating static pages (0/5) ...
Generating static pages (1/5)
Generating static pages (2/5)
Generating static pages (3/5)
✓ Generating static pages (5/5)
Finalizing page optimization ...
Collecting build traces ...
Route (app) Size First Load JS
┌ ○ / 131 B 103 kB
├ ○ /_not-found 995 B 103 kB
└ ƒ /api/hello 131 B 103 kB
+ First Load JS shared by all 102 kB
├ chunks/255-2dbbf79f36f0dfa2.js 46.4 kB
├ chunks/4bd1b696-c023c6e3521b1417.js 54.2 kB
└ other shared chunks (total) 1.88 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand$ curl -sI http://localhost:3000/
HTTP/1.1 200 OK
Vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding
x-nextjs-cache: HIT
x-nextjs-prerender: 1
x-nextjs-prerender: 1
x-nextjs-stale-time: 300
X-Powered-By: Next.js
Cache-Control: s-maxage=31536000
ETag: "3tiy3jm7rt31i"
Content-Type: text/html; charset=utf-8
Content-Length: 3954
Date: Wed, 30 Sep 2026 01:04:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5$ mv app/middleware.js middleware.js$ npx next build
▲ Next.js 15.5.26
Creating an optimized production build ...
✓ Compiled successfully in 2.5s
Linting and checking validity of types ...
Collecting page data ...
Generating static pages (0/5) ...
Generating static pages (1/5)
Generating static pages (2/5)
Generating static pages (3/5)
✓ Generating static pages (5/5)
Finalizing page optimization ...
Collecting build traces ...
Route (app) Size First Load JS
┌ ○ / 131 B 103 kB
├ ○ /_not-found 995 B 103 kB
└ ƒ /api/hello 131 B 103 kB
+ First Load JS shared by all 102 kB
├ chunks/255-2dbbf79f36f0dfa2.js 46.4 kB
├ chunks/4bd1b696-c023c6e3521b1417.js 54.2 kB
└ other shared chunks (total) 1.88 kB
ƒ Middleware 34.1 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand$ curl -sI http://localhost:3000/
HTTP/1.1 200 OK
x-middleware: hit
Vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding
x-nextjs-cache: HIT
x-nextjs-prerender: 1
x-nextjs-prerender: 1
x-nextjs-stale-time: 300
X-Powered-By: Next.js
Cache-Control: s-maxage=31536000
ETag: "rf70chnrrq31i"
Content-Type: text/html; charset=utf-8
Content-Length: 3954
Date: Wed, 30 Sep 2026 01:04:52 GMT
Connection: keep-alive
Keep-Alive: timeout=5— 2026-09-30 時点の出力
検証環境
- 検証日
- 実行環境
node:20Debian GNU/Linux 12 (bookworm)- バージョン
- Node.js 20.20.2
- npm 10.8.2
- Python 3.11.2
- Git 2.39.5
この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。
よくある原因
- 配置場所が間違っている:
app/middleware.tsやpages/middleware.tsは無効。
プロジェクトルート、またはsrc/を使う構成ならsrc/middleware.ts - matcher が狭すぎる:
matcher: "/api/:path*"だけだと通常ページで実行されない - export 名のミス:
myMiddlewareのような任意名で export すると、next buildは通るのに、実行時にすべてのリクエストが 500 Internal Server Error になる - 選んだランタイムで動かない API を使用: Edge runtime で
fs.readFileSyncなどを呼ぶと middleware ごと落ち、結果として実行されていないように見える
解決策
1. ルート配置を確認
my-app/
├── app/
├── src/ # src ディレクトリを使う場合
│ ├── app/
│ └── middleware.ts # ← ここ
├── middleware.ts # ← または ここ(src を使わない場合)
└── next.config.jsapp/middleware.ts ではない点に注意。
2. matcher を適切に書く
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
return NextResponse.next();
}
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};API ルートや静的アセットを除外しつつ全ページに効かせるテンプレ。
複数の matcher を配列で並べてもよい。
3. export 名は middleware
関数名は middleware 固定。
export function middleware(req: NextRequest) { /* ... */ }
// あるいは
export default function (req: NextRequest) { /* ... */ }auth や myMiddleware といった名前にしても、ビルドは成功してルート一覧にも ƒ Middleware が出る。
ところが実行時にはすべてのリクエストが 500 Internal Server Error になり、サーバーのログに次のエラーが出る(Next.js 15.5.26 の next build と next start で確認)。
Error: The Middleware "/middleware" must export a `middleware` or a `default` function静かにスキップされるのではなくサイト全体が落ちるので、500 が出たらまずこの行を探す。
4. ランタイムに合った API を使う
Edge runtime を選んだ場合は fs / child_process / Node.js 専用ストリーム等が使えない。
Node.js runtime は 15.5.0 で安定版になり、16.0.0 の proxy は既定が Node.js runtime である。
どちらを使うにせよ、重い処理や DB アクセスは Route Handler 側で行い、middleware は認証ヘッダの検査やリダイレクト判定に限定するのが無難。
export function middleware(req: NextRequest) {
const token = req.cookies.get("session")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}dev サーバを再起動して console.log がターミナルに出れば動いている合図。