フロントエンド

【Next.js 16】middlewareが開発環境だけ動かない原因|proxyへの改称とsrc配下に置くべき理由

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と本番ビルドの両方で確かめる

同じように「エラーが出ないのに動かない」で悩んでいる方の参考になれば幸いです。

参考

スポンサーリンク