メインコンテンツへスキップ
← 記事一覧に戻る
·開発·11 min read

Next.js middleware の例外ルートが本番だけ 404 になる罠 — Vercel env 未設定の落とし穴

Next.jsVercelmiddleware個人開発Claude Code

結論

ローカルで動く middleware 例外ルートが本番 Vercel でだけ 404 になる原因は、ほぼ確実に環境変数の未設定です。

vercel env ls production を一発叩いて、middleware が参照しているキーが production 欄に存在するか確認してください。存在しなければそれが原因です。コードは正しい。設定が抜けているだけです。


この記事の前提

筆者が個人開発している Next.js App Router + Vercel のポートフォリオアプリで、内部バッチ処理用の API ルート(/api/lab-tools/issue-finder/process-job)を Claude Code のスキルから自動実行する仕組みを構築しました。

外部からの不正アクセスを防ぐために、x-internal-key ヘッダーに一致した場合だけ通過させる middleware の例外ルートを実装しています。この設定はローカル開発環境(npm run dev)では完璧に動いていました。

スタック:

  • Next.js 16 App Router
  • Vercel(本番デプロイ)
  • Claude Code MAX(自動化スキル経由でバッチ実行)

読者のよくある詰まり

「middleware の例外ルートを実装したのに、なぜか本番だけ 404 が返る」

このパターンで詰まった経験がある方は多いはずです。理由は単純で、ローカルの .env.local と Vercel の environment variables は別々に管理する必要があるからです。

// middleware.ts — ローカルでは完璧に動く
import { NextRequest, NextResponse } from 'next/server'

export function middleware(req: NextRequest) {
  const internalKey = req.headers.get('x-internal-key')
  const expectedKey = process.env.ISSUE_FINDER_INTERNAL_KEY

  if (internalKey && internalKey === expectedKey) {
    return NextResponse.next()  // ← 内部キーが一致すれば通過
  }

  // 以降は通常の認証フロー
  return NextResponse.next()
}

このコードは何も間違っていません。しかし process.env.ISSUE_FINDER_INTERNAL_KEY が本番 Vercel に登録されていなければ、expectedKeyundefined になります。

undefined === undefinedtrue なので、すべての x-internal-key ヘッダー付きリクエストが通過してしまうと思いきや——実際には middleware が認証フローへ飛ばす前に別のルートが 404 を返すケースもあります(ミドルウェアの評価順、matcher の設定によって挙動は変わります)。

今回の実録では、404 が返り続けました。


実際に起きたこと

事象

Claude Code のスキルから本番 API エンドポイントに POST を送信:

curl -X POST https://ihara-frontend.com/api/lab-tools/issue-finder/process-job \
  -H "x-internal-key: $ISSUE_FINDER_INTERNAL_KEY" \
  -H "Content-Type: application/json"

結果: 404 が返り続ける。

最初の仮説と検証

  1. middleware.ts のコードが間違っている? → ローカルで npm run dev して同じリクエストを送ると 200 OK、8 件 insert 成功。コードは正しい。
  2. matcher の設定ミス?matcher を確認。/api/lab-tools/:path* を明示的に除外しているので問題なし。
  3. スキルのドキュメントが古い? → スキルの説明書には「本番 middleware の INTERNAL_KEY 経由で通る」と書いてあった。

原因判明

vercel env ls production | grep ISSUE_FINDER

出力: なし(空)

ISSUE_FINDER_INTERNAL_KEY が production 環境に登録されていませんでした。ローカルの .env.local には書いてあるのに、Vercel の production 環境変数への登録を忘れていたのです。

2026-05-20 朝、自動化スキルから本番 API に POST が通らず、手動で dev server を立ち上げて localhost 経由で実行することで回避しました。vercel env ls production を叩いたら ISSUE_FINDER_INTERNAL_KEY が空で、即座に原因が判明。env 登録後は本番でも正常に動作しています。


原因分析

なぜこのミスが起きるのか。構造的な理由が 2 つあります。

理由 1: ローカルと本番で環境変数は独立している

Next.js 開発時に使う .env.local は Vercel には自動的に反映されません

場所環境変数の管理方法
ローカル開発.env.local(git には含めない)
Vercel productionVercel Dashboard → Settings → Environment Variables
Vercel preview同上(production と別設定可能)

.env.local で動作確認 → Vercel にデプロイ、という流れを繰り返していると、「ローカルで動いた → 本番でも動くはず」という思い込みが生まれます。

理由 2: middleware のエラーは静かで分かりにくい

middleware が原因で 404 を返す場合、エラーログに明示的な「middleware error」は残りません。アプリケーションの API ルートが見つからないのか、middleware が弾いているのか、外側からは区別がつきません。

Vercel のログを見ると、404 の記録はあっても「なぜ 404 なのか」の原因は記録されていません。これがデバッグを長引かせます。


判断基準: ローカルで動くのに本番で 404 になる時のチェックリスト

# Step 1: 本番の環境変数を確認
vercel env ls production

# Step 2: middleware が参照しているキーが存在するか確認
vercel env ls production | grep <キー名>

# Step 3: 存在しなければ追加
vercel env add <キー名> production

# Step 4: 再デプロイ(env 追加後は再デプロイが必要)
vercel deploy --prod

これで解決しない場合は、次を疑います:

  1. matcher が正しく設定されているかmatcher の除外パターンを確認
  2. middleware の実行順序 — Edge Runtime での実行タイミング
  3. Vercel Edge Network のキャッシュ — Vercel のコントロールパネルからキャッシュを purge

dev server の hot reload エラーとの組み合わせ

余談ですが、同日に別の問題も踏みました。

本番 API が 404 を返す中で、dev server を使って回避しようとしたところ、dev server(PORT=3010)が「missing required error components, refreshing...」という HTML を返し続ける状態に陥りました。

GET /api/lab-tools/issue-finder/jobs は 200 を返すのに、POST /api/lab-tools/issue-finder/process-job だけ 404 HTML を返す状態。

解決策: 別ポートで起動していた dev server(PORT=3000)に切り替えたら問題なく動いた。

これは Next.js の middleware hot reload バグで、複数の dev server インスタンスが同じポートを争うとこういう状態になることがあります。根本解決は dev server を完全に停止してから再起動することですが、別ポートへの切り替えが最速の回避策でした。


今日やること(3 つ以内)

  1. 既存の内部 API を持っているなら今すぐ確認: vercel env ls production で必要な env が全部登録されているか照合する
  2. スキル・ドキュメントに「本番確認手順」を追記: 「本番で動くか確認するには vercel env ls で env を確認→本番エンドポイントに curl テスト」をチェックリストに入れる
  3. 自動化スキルに疎通チェックを追加: スキル実行前に本番エンドポイントへの ping チェックを入れ、404 が返る場合は dev server fallback か早期失敗させる

で、どう稼ぐ?

内部 API が止まっていても、Claude Code のスキルはエラーを静かに飲み込むことがあります。今回のケースでいえば、バッチが「実行した体」で完了していても DB への insert は 0 件、というデータ欠損が無音で起きていました。

これが収益化インフラに直結する処理(Stripe の Webhook 処理、メルマガ配信履歴の記録、購入フラグの更新)だったら? 気づかないまま顧客への配信が止まり、課金だけ続く、という状況が発生します。

自動化の恩恵は「気づかなくても動いていること」ですが、逆側のリスクは「気づかなくても止まっていること」です。

月 5 万を目指す個人開発者がやるべきことは一つ: 自動化 routine に疎通チェックを入れること。 失敗時に Slack や LINE に通知するだけでいい。完璧な監視は不要です。「止まっていることに気づける」だけで、ユーザーへの影響を最小化できます。


masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通

masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

Next Step

次に読むならこの導線です

すべての記事を見る
有料で次へ進む¥1,000

【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード

Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。

次の実験記録も追う

Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

この記事が役に立ったらシェア