できない.dev

Next.js の middleware が実行されない

middleware.ts はプロジェクトルートに 1 ファイル限定で、matcher の設定と Edge runtime 制約に従う必要がある。
配置と matcher がずれると静かにスキップされ、export 名を間違えるとリクエストがすべて 500 になる。

公開: 更新:

実行例あり(2026-09-30 に実環境で検証)

要約

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:20 Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. 配置場所が間違っている: app/middleware.ts や pages/middleware.ts は無効。
    プロジェクトルート、または src/ を使う構成なら src/middleware.ts
  2. matcher が狭すぎる: matcher: "/api/:path*" だけだと通常ページで実行されない
  3. export 名のミス: myMiddleware のような任意名で export すると、next build は通るのに、実行時にすべてのリクエストが 500 Internal Server Error になる
  4. 選んだランタイムで動かない API を使用: Edge runtime で fs.readFileSync などを呼ぶと middleware ごと落ち、結果として実行されていないように見える

解決策

1. ルート配置を確認

my-app/
├── app/
├── src/                       # src ディレクトリを使う場合
│   ├── app/
│   └── middleware.ts          # ← ここ
├── middleware.ts              # ← または ここ(src を使わない場合)
└── next.config.js

app/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 がターミナルに出れば動いている合図。

この記事は役立ちましたか?