Next.js 16のサイトで、middleware.tsにリダイレクトの処理を追加しました。ところが、開発サーバー(next dev)ではリダイレクトがまったく効かず、エラーも警告も出ません。原因はコードではなく、ファイルの置き場所でした。
この記事の検証結果は、Next.js 16.3.8(2026年10月時点の最新版)で確認したものです。実際に問題が起きたのは16.2系の開発環境でした。
結論:src/app構成ならsrc/proxy.tsに置く
先に結論です。
- Next.js 16では
middlewareがproxyに改称されました。新しく書くならproxy.tsにproxy関数を書きます。 src/app構成のプロジェクトでは、src/proxy.ts(appと同じ階層)に置きます。- リポジトリ直下に置くと、エラーも警告も出ないまま無視されることがあります。しかも
next devと本番ビルドで結果が違うことがあります。 middleware.tsとproxy.tsを両方置くとビルドエラーになります。
検証した結果は次のとおりです(src/app構成、Next.js 16.3.8)。
| 置いたファイル | next dev |
next build + next start |
|---|---|---|
直下proxy.ts |
無視される | 無視される |
直下middleware.ts |
無視される | 動く |
src/proxy.ts |
動く | 動く |
src/middleware.ts |
動く(非推奨の警告あり) | 動く(非推奨の警告あり) |
src/proxy.tsとsrc/middleware.tsの両方 |
起動するがエラーで応答しない | ビルドエラー |
直下のmiddleware.tsは、開発環境では動かないのに本番では動くという、いちばん気づきにくい状態になります。
発生した症状:エラーなしでリダイレクトが効かない
やりたかったのは、古いURLへのアクセスを新しいURLに301で転送することでした。
// middleware.ts(リポジトリ直下に置いていた)
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/old-blog/")) {
const url = request.nextUrl.clone();
url.pathname = url.pathname.replace("/old-blog/", "/blog/");
return NextResponse.redirect(url, 301);
}
}
next devで/old-blog/helloを開いても、転送されずに404になります。ターミナルにもブラウザにもエラーは出ません。
環境
- Next.js 16系(App Router、開発サーバーはTurbopack)
- ディレクトリ構成:
src/appを使用 - 実行環境:Node.js
まず疑ったこと:matcherの書き方
最初は、処理の対象パスを絞るconfig.matcherの書き方を疑いました。しかしmatcherを外しても、全リクエストに対してconsole.logを入れても、何も出力されません。関数がそもそも呼ばれていない、つまりファイルが読み込まれていないと判断しました。
原因1:Next.js 16でmiddlewareはproxyに改称された
Next.js 16では、middlewareというファイル名が非推奨になり、proxyに変わりました。書き方はほぼ同じで、ファイル名と関数名を変えるだけです。
// src/proxy.ts
import { NextResponse, type NextRequest } from "next/server";
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/old-blog/")) {
const url = request.nextUrl.clone();
url.pathname = url.pathname.replace("/old-blog/", "/blog/");
return NextResponse.redirect(url, 301);
}
}
export const config = {
matcher: ["/old-blog/:path*"],
};
公式のcodemodで自動変換もできます。
npx @next/codemod@canary middleware-to-proxy .
middleware.tsはまだ動きますが、起動時に次の警告が出ます。
⚠ The "middleware" file convention is deprecated. Please use "proxy" instead.
原因2:src/app構成ではsrc/配下に置く
今回の直接の原因はこちらです。公式ドキュメントでは、proxy.tsはappやpagesと同じ階層に置くと定められています。src/app構成ならsrc/proxy.tsです。
直下に置いたファイルは、検証した範囲では次のように扱われました。
- 直下の
proxy.ts:開発でも本番でも無視される。ビルドも通り、警告も出ない - 直下の
middleware.ts:next devでは無視されるが、本番ビルドでは読み込まれる
直下のmiddleware.tsが本番で動くのは、古い構成との互換のためと考えられますが、公式ドキュメントに記載はありません。頼らずにsrc/配下へ移すのが確実です。
原因3:middlewareとproxyは同時に置けない
移行の途中で両方のファイルを置くと、ビルドが失敗します。
Error: Both middleware file "./src/src/middleware.ts" and proxy file "./src/src/proxy.ts" are detected. Please use "./src/src/proxy.ts" only.
パスがsrc/src/と二重に表示されますが、検証時の出力そのままです(next build時のみ)。気にせずmiddleware.tsを消してください。
next devでは起動はするものの、同じエラーが出てページが応答しなくなります。
補足:proxyはNode.jsランタイムで動く
Next.js 16のproxyは、標準でNode.jsランタイムで動きます。そのため、Basic認証のユーザー名などをprocess.envから実行時に読む処理も書けます。
一方で、proxyではruntimeの設定ができず、Edgeランタイムは使えません。Edgeで動かしたい場合は、当面middlewareのまま使うことになります。
確認方法:全リクエストを401にして読み込みを確かめる
ファイルが読み込まれているかは、わざと全リクエストを止めるとすぐにわかります。
// src/proxy.ts(確認用。確認後は必ず元に戻す)
import { NextResponse } from "next/server";
export function proxy() {
return new NextResponse("blocked", { status: 401 });
}
curl -i http://localhost:3000/
# HTTP/1.1 401 Unauthorized → 読み込まれている
# HTTP/1.1 200 OK → 読み込まれていない
本番ビルドでは、next buildの出力に次の行があるかも確認できます。
ƒ Proxy (Middleware)
next devと本番ビルドの両方で確認するのがポイントです。片方だけでは、今回のような食い違いに気づけません。
チェックリスト
- ファイル名は
proxy.ts、関数名はproxy(またはdefault export)になっているか src/app構成なら、src/proxy.tsに置いているかmiddleware.tsが残っていないか(両方あるとビルドエラー)next devとnext buildの両方で、読み込まれていることを確認したか- Edgeランタイムを前提にした処理が含まれていないか
よくある質問
Q. middleware.tsのままでも動くなら、急いで変える必要はありますか?
A. 今すぐ動かなくなるわけではありませんが、非推奨です。将来のバージョンで削除される可能性があるため、codemodでproxyに移しておくことをおすすめします。
Q. src/を使っていないプロジェクトはどうなりますか?
A. appがリポジトリ直下にあるなら、proxy.tsも直下に置きます。基準は「appやpagesと同じ階層」です。
Q. 読み込まれていないのに、なぜ警告が出ないのですか?
A. 検証した範囲では、置き場所が違うファイルはproxyの対象として検出されず、警告も出ませんでした。直下のmiddleware.tsは本番ビルドでだけ検出されるため、開発環境では気づきにくくなっています。
まとめ
- Next.js 16では
middlewareがproxyに改称された。codemodで移行できる proxy.tsはappと同じ階層に置く。src/app構成ならsrc/proxy.ts- 置き場所が違うと、エラーも警告もなく無視される。直下の
middleware.tsは開発と本番で結果が変わる middleware.tsとproxy.tsを両方置くとビルドエラー- 読み込みは、全リクエストを401にして
next devと本番ビルドの両方で確かめる
同じように「エラーが出ないのに動かない」で悩んでいる方の参考になれば幸いです。
参考
- File-system conventions: proxy.js – Next.js Docs
- Upgrading: Version 16 – Next.js Docs
- Renaming Middleware to Proxy – Next.js Docs
