
目次
こんにちは、マサシです。
千葉でWeb制作をしています(スタジオについて)。
このサイト、ずっとNext.js 15.5で動かしていました。
16が出たのは2025年10月なので、もう9ヶ月も前です。
「そろそろ上げないとな」と思いつつ、稼働中サイトのメジャーアップデートって腰が重いんですよね。
で、先日ようやく上げました。
このサイト本体と、Basic認証つきで動かしている小さめの社内ツールの2つ。
横並びで16に上げてみたら、片方はあっさり通って、片方はビルドが落ちました。
落ちた原因は「昔の自分が書いたwebpack設定」でした。
そのあとに検証したnext/imageの罠も、想像よりずっと意地悪だった。
なので、実際のログとスクショつきで全部書いておきます。
先に結論
急いでいる人向けに、要点は3つです。
・next.configにwebpack設定が残っていると、Next.js 16はビルド自体が失敗する(16からTurbopackが既定になったため)。設定を消すか、next build --webpackで逃げる
・next/imageのlocalPatternsは、書き方を間違えると「ビルドは通るのに本番で画像だけ全滅」になる。devと本番で壊れ方が違うのが本当に意地悪
・15.5の時点でasync対応(paramsやcookiesのawait化)が済んでいれば、アプリコード側の変更はほぼゼロだった
検証した環境と、確認できたこと・できていないこと
| 内容 | |
|---|---|
| 対象 | 稼働中のNext.js 15.5.20プロジェクト2つ(このサイト本体+Basic認証つき社内ツール) |
| 上げた先 | Next.js 16.2.11/React 19.2.8 |
| 環境 | Node.js v22.22.1/npm 10.9.4 |
| 確認できたこと | ビルドが落ちる条件と直し方・middleware→proxyの実挙動・localPatternsのdev/build/本番の挙動差(すべて実行して確認) |
| 確認できていないこと | Pages Routerのプロジェクト・edgeランタイム依存のmiddleware・Vercel以外へのデプロイ |
検証はいきなり本番に当てず、リポジトリを作業用フォルダにコピーして行いました。
アップグレード自体は手動です。
npm install next@latest react@latest react-dom@latest @types/react@latest @types/react-dom@latest
一括でやってくれるcodemod(npx @next/codemod@canary upgrade latest)もあります。
ただ今回は「何がどう壊れるか」を見たかったので、あえて素で上げました。
1つ目のプロジェクトは、拍子抜けするほど無風だった
小さい社内ツールの方から上げました。
インストールして、npm run build。
……通りました。
初回で、です。
変わったことは3つだけです。
・⚠ The "middleware" file convention is deprecated. Please use "proxy" instead. の警告が出る(ビルドは通る)
・tsconfig.jsonが黙って書き換えられる(jsxがreact-jsxに。ログに「mandatory changes were made」と出ます)
・ビルド出力から「First Load JS」の欄が消えた(公式いわく、Server Components時代には不正確な指標だったため削除)
コンパイル時間は5.3秒→2.1秒になりました。
Turbopackがビルドでも既定になった効果ですね。
middleware→proxyのリネームもやってみた
警告のまま放置でも動きますが、せっかくなのでproxyに移行しました。
やることは2つだけ。
mv middleware.ts proxy.ts
// 関数名も middleware → proxy に変える
export function proxy(req: NextRequest): NextResponse {
このツールはmiddlewareでBasic認証をかけています。
挙動が変わっていないか、next startで確かめました。
・認証なし → 401
・正しいID/パスワード → 200
・間違ったパスワード → 401
3パターンともリネーム前と同じでした。
ひとつ注意があって、proxyのランタイムはNode.js固定です(edge非対応)。
edgeランタイム前提のmiddlewareを持っている場合は、まだリネームしない方がいいです。

警告が出てるだけなら、middlewareのまま放置でもいいんですか?

16の間は動くよ。ただ非推奨は「次のメジャーで消える候補」だからね。edge依存がないなら、リネーム2ヶ所だけだし早めに移すのが安全だよ。
2つ目(このサイト本体)は、ビルドが落ちた
同じ手順でこのサイトを上げたら、npm run buildで落ちました。
⨯ ERROR: This build is using Turbopack, with a `webpack` config and no `turbopack` config.
This may be a mistake.
NOTE: your `webpack` config may have been added by a configuration plugin.
最初の感想は「webpack設定なんて書いた覚えないけど……」でした。
エラー文の「pluginが追加した可能性」に引っ張られて、まずexperimental.optimizeCssを疑いました(内部でwebpack系の処理を使うやつです)。
外してみたけど、まだ落ちる。
で、next.config.tsを下までスクロールしたら、ありました。
100行目過ぎに、昔の自分が書いたCSS最適化のwebpack:ブロックが。
// CSSの最適化
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {
config.optimization.splitChunks = {
// ...チャンク分割のカスタマイズ
};
}
return config;
},
数年運用しているconfigって、こういうのが下の方に埋まってるんですよね……。
「書いた覚えがない」は当てになりません。
最初にgrep webpack next.config.tsを打つのが正解でした。
直し方は2択
直す場合は、webpack:ブロックを消します。
Turbopackはチャンク分割を内蔵しているので、この手の最適化ブロックはそのまま消せました。
消した結果、ビルドが通って31.2秒→4.2秒。
クリーンビルド同士の比較で、体感でも別物です。
今すぐ直せない場合は、next build --webpackで従来のwebpackビルドに戻せます。
元のconfigのまま試したら、こちらも普通に通りました(29.8秒。15のときとほぼ同じ時間です)。
カスタムloaderが多くてTurbopack移行が重いプロジェクトもあると思います。
その場合は、まず--webpackで16に上げてしまって、Turbopack対応を別作業に切り出すのが現実的です。
next/imageのlocalPatterns、想像の3倍意地悪だった
ここからが本題です。
Next.js 16から、ローカル画像にクエリパラメータを付けると怒られるようになりました。
<Image src="/icon.png?v=1" />みたいな、キャッシュ更新用の定番の書き方のことです。
うちの2プロジェクトはたまたま使っていなかったので、検証ページを作って実際に踏んでみました。
まず、素直に踏むとこうなる
?v=1付きのローカル画像を置いてビルドすると、こう落ちます。
Error: Image with src "/icon-192.png?v=1" is using a query string
which is not configured in images.localPatterns.
devサーバーで開くと、エラー画面はこうです。

注意したいのは、これがコンパイルではなく静的生成(prerender)の段階で落ちることです。
devで該当ページを開いていれば一目瞭然ですが、開かずにビルドだけ回していると、CIで初めて発覚するタイプの地雷です。
「?v=1を許可すればいいんでしょ」で書くと、静かな事故になる
エラー文に従って、こう書いたとします。
images: {
localPatterns: [
{ pathname: "/**", search: "?v=1" },
],
},
これでビルドは通ります。
全ページの静的生成も成功します。
ところがlocalPatternsは、1つでも書いた瞬間にホワイトリストになります。
search: "?v=1"は「クエリが?v=1のものだけ許可」の意味です。
なので、クエリなしの普通の画像が全部リストから漏れます。
実測したら、壊れ方が環境ごとに違いました。
| 環境 | この設定での挙動(実測) |
|---|---|
next dev | クエリなし画像を使うページを開いた瞬間500エラー(派手に気づける) |
next build | 通る。全ページの生成も成功する |
本番(next start) | ページは200で普通に開く。でも画像最適化のリクエストが全部400になる("url" parameter is not allowed) |
devでは大騒ぎするのに、ビルドは黙って通して、本番ではページが開くのに画像だけ全滅する。
devで確認せずにデプロイすると、一番嫌な形で本番に着弾します。
皮肉なことに、?v=1を付けた画像だけは生きているんですよね。
正解のconfig
クエリなしの通常画像と、キャッシュ更新用の?v=1だけを通す書き方はこうでした。
images: {
localPatterns: [
{ pathname: "/**", search: "" }, // クエリなしの画像
{ pathname: "/**", search: "?v=1" }, // キャッシュ更新用
],
},
search: ""が「クエリなし」の意味です。
この設定で、画像最適化のエンドポイントを3パターンcurlで確かめました。
・通常画像(クエリなし)→ 200
・?v=1付き → 200
・?foo=bar(意図しない任意クエリ)→ 400で拒否
修正後は、検証ページも通常ページも普通に表示されます。


面倒だから `{ pathname: "/**" }` だけ書いて、全部許可しちゃダメなんですか?

動くには動くよ。searchを省略すると任意クエリまで通る(これも実測で200だった)。ただこの制限はもともと、クエリを変えながら画像最適化を大量に叩かせる攻撃への対策なんだ。公式も「searchは省略せず具体的な値を」と言っているから、実際に使うクエリだけ列挙するのが筋だね。
ついでに見ておきたい画像まわりの変更
configを見直していて気づいたんですが、うちは画像のqualitiesを以前から設定していたおかげで、無風で通過した変更もありました。
16からはqualitiesの既定が[75]だけになります。
quality={90}のような指定をしているサイトは、設定に無い値だと近い値に丸められるので要注意です。
他にもminimumCacheTTLの既定が60秒→4時間になるなど、画像まわりは細かい変更が多めでした。
アップグレード前チェックリスト
今回の実測をふまえて、15.5から16に上げる前に見る場所をまとめます。
・grep webpack next.config.ts → ヒットしたら「消す」か「--webpack」かを先に決める
・experimental系のフラグも一緒に見直す(webpack依存のものが混ざっている)
・middleware.tsがedgeランタイム依存かを確認 → 依存なしならproxy.tsへ、依存ありならリネームは待つ
・next/imageのsrcにクエリ付きローカル画像がないかgrep → あるならlocalPatternsをsearch付きで設計する
・localPatternsを書いたら、devで主要ページを実際に開いて確認する(ビルド成功を信用しない)
・Node.js 20.9以上・TypeScript 5.1以上かを確認
コード側は、15.5の時点でasync request APIs(await paramsなど)への対応が済んでいれば、ほぼ何もありませんでした。
15.0〜15.4から一気に上げる場合は、まずそこが壁になると思います。
まとめ
・「This build is using Turbopack, with a webpack config」で落ちたら、まず自分のnext.config.tsをgrepする。犯人は大体、昔の自分
・localPatternsはホワイトリスト。search: "?v=1"だけ書くと、ビルドは通るのに本番で画像が静かに全滅する。search: ""との2本立てが正解
・逃げ道は用意されている(--webpack・middleware据え置き)。全部を一度に直す必要はない
ちなみに、アップグレード後にdevで謎のエラーが出る場合は、.nextフォルダの削除で直ることもあります。
そのパターンは別の記事に書きました → Next.jsの「Expected clientReferenceManifest to be defined」は大体バグじゃない
同じところでハマっている人の時間が、少しでも浮けば。

