Next.js 15のサポート(Maintenance LTS)は、2026年10月21日で終わります。10月22日以降に公表された脆弱性には、15系の修正版が出ない前提で計画を立てる必要があります。この記事は、期限までに16系へ移る判断をしたあと、「実際に何をどの順番で直すのか」を知りたい開発担当の方に向けて書いています。
公式の自動変換ツール(codemod)を実行すれば、作業の多くは機械的に進みます。ただし、画像の既定値の変更や独自のwebpack設定のように、codemodが触らない箇所が残ります。この記事では、移行の順番と、codemodのあとに残る箇所を洗い出すためのスクリプトをまとめます。
先に、要点をまとめます(2026年10月6日確認)。
- 順番は「Node.js 20.9以上を確認 → 15.5.27に上げる → codemodを2つ実行 → 残りをgrepで洗い出して手で直す → buildと画面確認」です。codemodは
upgrade latest と、params などを非同期に書き換える next-async-request-api の2つを使います
- codemodのあとに残るのは、画像の既定値(画質・キャッシュ時間・クエリ付き画像)、独自のwebpack設定、Parallel Routesの
default.js、revalidateTag の引数、runtimeConfig などです。見落とすとbuildが止まるものと、buildは通るのに表示が変わるものがあります
- 移行の工数は、codemodのあとに残る箇所の数と、画面確認を自動テストでどこまで済ませられるかで決まります。期限が迫っている場合は、15.5.27への更新を先に済ませてから移行に入ります
いつまでに移行するか、移行にどれくらいの費用がかかるかの判断は、Next.js 15のサポート期限は2026年10月21日|EOLで何が起きるか・16への移行で決めることで整理しています。この記事はその続きの、手を動かす段階の話です。
15から16への移行は、どの順番で進める?
前提の確認、15.5.27への更新、codemodの実行、残りの洗い出しと手直し、buildと画面確認の順に進めます。codemodの前に15.5.27を当てておくと、移行が長引いても9月30日の修正が入った状態で運用できます。

- 前提を確認する:Next.js 16はNode.js 20.9.0以上、TypeScript 5.1.0以上が必要です。Node.js 18では動きません。本番のサーバーやCIのNode.jsの版も合わせて確認します
- 先に15.5.27へ上げる:9月30日のセキュリティ修正のうち3件は15.5.27に入っています(詳細はNext.js 16.3.8・15.5.27の脆弱性7件)。15.5.26からの更新なら変更点は修正3件だけなので、移行とは別に短時間で当てられます
- codemodを2つ実行する:
upgrade latest と next-async-request-api を実行し、差分を読んでからコミットします
- 残りをgrepで洗い出す:後述のスクリプトで、codemodが触らない箇所を一覧にします
- 手で直してbuildする:
next build が通るまで直します。16では next build がlintを実行しなくなるため、lintは別に実行します
- 画面で確かめる:画像の画質、キャッシュの更新、ログイン前後の遷移など、buildでは分からない変化を確認します
10月6日時点で16系の最新の修正版は16.3.8です(9月30日公開)。upgrade latest は最新の安定版を入れるため、移行が終わった時点で16.3.8以上になっているかを npm ls next で確かめます。
codemodは何を直して、何を残す?
upgrade latest は、next.configのTurbopack設定、next lint からESLint CLIへの移行、middlewareからproxyへの改名、unstable_ 接頭辞の削除などを書き換えます。params や cookies() の非同期化は別のcodemodで、画像の既定値や独自のwebpack設定は手で直します。
公式の移行ガイド(How to upgrade to version 16)をもとに、自動で直る範囲と手で直す範囲を分けると次のようになります。
| 変更点 | codemodで直るか | 直らない場合の扱い |
|---|
experimental.turbopack をトップレベルの turbopack へ | upgrade で直る | — |
next lint の削除 | upgrade で直る(ESLint CLIへ) | next.configの eslint キーは削除する |
middleware → proxy の改名 | upgrade で直る | Edge Runtimeが必要な処理は後述 |
unstable_cacheLife などの接頭辞 | upgrade で直る | — |
params・searchParams・cookies()・headers()・draftMode() の同期アクセス | next-async-request-api で直る | 型は npx next typegen で生成できる |
| 独自のwebpack設定 | 直らない | Turbopackに移すか、next build --webpack を指定する |
| 画像の既定値(画質・キャッシュ・クエリ付き画像など) | 直らない | next.configに必要な値を書く |
Parallel Routesの default.js | 直らない | スロットごとに作る(ないとbuildが失敗) |
revalidateTag の第2引数 | 直らない | 'max' などの cacheLife プロファイルを渡す |
serverRuntimeConfig・publicRuntimeConfig | 直らない | 環境変数に置き換える |
| AMP | 直らない | 削除された機能なので、AMPのページを作り直す |
実行するコマンドは次のとおりです。
# 本体・React・設定の書き換え
npx @next/codemod@canary upgrade latest
# params や cookies() の同期アクセスが残っている場合
npx @next/codemod@canary next-async-request-api .
公式ガイドは @canary を付けたコマンドを案内しています。upgrade のcodemodは非同期APIの書き換えまでは行わないと明記されているため、15系で互換のための同期アクセスを残しているアプリでは、2つ目も実行します。
codemodで直らない箇所は、どう洗い出す?
codemodのあとに、プロジェクトのルートで次のスクリプトを実行します。ファイルは変更せず、手で直す候補を項目ごとに一覧にします。
このスクリプトは、上の表の「直らない」項目と、buildは通るのに挙動が変わる項目をgrepで探します。私たちが管理するNext.jsのリポジトリ(15系1件、16系5件)と、問題のある書き方をわざと入れた検証用のフォルダで実行し、検出の漏れと誤検出を確認してから掲載しています。
#!/usr/bin/env bash
# Next.js 15 → 16 移行前の事前チェック(読み取りのみ)
set -u
SRC="app src pages components lib"
hit() { echo "■ $1"; echo "$2" | sed 's/^/ /'; echo; }
g() { grep -rnE --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.mjs' "$1" $SRC 2>/dev/null; }
echo "Node.js: $(node -v)(16は20.9.0以上が必要)"
echo "next: $(node -p "require('next/package.json').version" 2>/dev/null || echo '未インストール')"
f=$(ls middleware.* src/middleware.* 2>/dev/null)
[ -n "$f" ] && hit "middleware を proxy に改名(proxy は Node.js ランタイム固定)" "$f"
r=$(grep -nE "webpack\s*[:(]" next.config.* 2>/dev/null)
[ -n "$r" ] && hit "next.config に webpack 設定あり(build が失敗する)" "$r"
r=$(grep -nE "next lint" package.json 2>/dev/null; grep -nE "^\s*eslint\s*:" next.config.* 2>/dev/null)
[ -n "$r" ] && hit "next lint は削除済み(ESLint CLI へ移行)" "$r"
r=$(g "quality=\{?\"?[0-9]+" | grep -vE "quality=\{?\"?75\b")
[ -n "$r" ] && hit "quality が 75 以外(images.qualities に追加が必要)" "$r"
r=$(g "src=\"/[^\"]*\?[^\"]*\"")
[ -n "$r" ] && hit "クエリ付きのローカル画像(images.localPatterns の search が必要)" "$r"
r=$(grep -nE "domains\s*:" next.config.* 2>/dev/null; g "next/legacy/image")
[ -n "$r" ] && hit "images.domains / next/legacy/image は非推奨" "$r"
for d in $(find app src/app -type d -name '@*' 2>/dev/null); do
ls "$d"/default.* >/dev/null 2>&1 || echo "■ default.js がないスロット(build が失敗する): $d"
done
r=$(g "revalidateTag\([^,()]*\)")
[ -n "$r" ] && hit "revalidateTag の第2引数が必要" "$r"
r=$(g "unstable_(cacheLife|cacheTag|rootParams)"; grep -nE "dynamicIO|useCache|ppr" next.config.* 2>/dev/null; g "experimental_ppr")
[ -n "$r" ] && hit "unstable_ 接頭辞・実験フラグの削除" "$r"
r=$(g "next/config|serverRuntimeConfig|publicRuntimeConfig"; grep -nE "RuntimeConfig" next.config.* 2>/dev/null)
[ -n "$r" ] && hit "runtimeConfig は削除(環境変数へ)" "$r"
r=$(g "next/amp|amp\s*:\s*true")
[ -n "$r" ] && hit "AMP は削除" "$r"
files=$(find app src/app -type f \( -name 'page.*' -o -name 'layout.*' -o -name 'default.*' \) 2>/dev/null)
if [ -n "$files" ]; then
r=$(grep -lE "(^|[^.[:alnum:]_])(params|searchParams)\.|cookies\(\)\.|headers\(\)\." $files 2>/dev/null | xargs -r grep -LE "await (props\.)?(params|searchParams)|use\((props\.)?params" 2>/dev/null)
[ -n "$r" ] && hit "params / searchParams を await せずに読んでいる疑い" "$r"
fi
r=$(grep -rnE "scroll-behavior:\s*smooth" --include='*.css' --include='*.scss' app src styles 2>/dev/null)
[ -n "$r" ] && hit "scroll-behavior: smooth(遷移時の上書きが既定でなくなる)" "$r"
r=$(g "generateSitemaps|generateImageMetadata")
[ -n "$r" ] && hit "sitemap / OG画像の id・params が Promise になる" "$r"
結果の読み方は次のとおりです。
- buildが止まる項目:webpack設定、
default.js のないスロット、revalidateTag の1引数呼び出し(TypeScriptのエラーになります)。ここは直さないと先に進めません
- buildは通るが表示が変わる項目:
quality の値、クエリ付きのローカル画像、scroll-behavior。画面確認の対象に入れます
- 疑いとして出す項目:
params の同期アクセスは、const { slug } = params のような書き方を確実には拾えません。逆に、await の書き方によっては対応済みのファイルも出ます。一覧は確認の入口として使い、最終的には next build と型チェックで判断します
route.ts は検査の対象から外しています。const { searchParams } = new URL(request.url) のように、Next.jsの searchParams ではない変数を読むルートハンドラーが多く、16系のリポジトリで誤検出が出たためです。ルートハンドラーの第2引数で受け取る params は、next-async-request-api のcodemodに任せます。
middleware.ts は、16系でも非推奨の警告を出しながら動きます。私たちが確認した16系のリポジトリにも、改名せずに残っているものがありました。期限に追われている場合は後回しにしても動きますが、upgrade のcodemodが書き換えるので、移行と同時に済ませるのが手間は少なくなります。
見落としやすい既定値の変更は?
画像まわりの既定値が6つ変わっています。どれもbuildは通るため、画面を見るまで気づきにくい変更です。特に、quality に75以外を指定している画像は、何もしないと画質が75に寄せられます。
| 設定 | 15まで | 16から | 影響が出る例 |
|---|
images.qualities | すべての値を許可 | [75] のみ | quality={90} の商品画像が75で配信される |
images.minimumCacheTTL | 60秒 | 4時間(14400秒) | 元画像を差し替えても、最適化済みの画像が最大4時間残る |
images.imageSizes | 16を含む | 16を削除 | 16pxのアイコンを next/image で出している場合 |
| ローカル画像のクエリ | そのまま配信 | localPatterns.search の指定が必要 | /logo.png?v=2 のようにキャッシュ対策でクエリを付けている |
| ローカルIPへの画像最適化 | 許可 | 既定で拒否 | VPC内の画像サーバーを参照していて400が返る |
images.maximumRedirects | 無制限 | 3回 | 短縮URL経由の外部画像 |
qualities は、指定した quality が配列にない場合、最も近い値に寄せられます。複数の画質を使っているなら、next.configに使う値をすべて書きます。
// next.config.ts
const nextConfig: NextConfig = {
images: {
qualities: [60, 75, 90],
minimumCacheTTL: 60, // 元画像を頻繁に差し替える運用なら、15と同じ値に戻す
},
}
ローカルIPの制限を外す dangerouslyAllowLocalIP は、名前のとおりSSRF(サーバーを踏み台にして内部のネットワークへアクセスされる攻撃)の危険がある設定です。公式ガイドも、プライベートネットワークに限って、リスクを理解したうえで有効にするよう書いています。9月30日に修正された脆弱性のうちHighの1件も、Image OptimizationのSSRFでした。
画像以外では、次の2つが挙動の変わる変更です。
scroll-behavior: smooth:15まではページ遷移のときにNext.jsが一時的にスムーズスクロールを止めていましたが、16からは止めません。15と同じ動きにするには、<html> に data-scroll-behavior="smooth" を付けます
- next.configの読み込み:
next dev のときに process.argv に dev が含まれなくなりました。設定ファイルの中で開発時だけの処理を分けている場合は、process.env.NODE_ENV で判定します
middlewareをproxyに変えるとき、何に気をつける?
proxyはNode.jsのランタイムで固定され、Edge Runtimeを選べません。Edge Runtimeでなければ動かない処理をmiddlewareに書いている場合は、16に上げてもmiddlewareのまま残します。
改名そのものはcodemodが行います。手で変える場合は、ファイル名を proxy.ts に、関数名を proxy にします。next.configの skipMiddlewareUrlNormalize も skipProxyUrlNormalize に変わります。
// proxy.ts(旧 middleware.ts)
import { NextResponse, type NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// 処理の内容は middleware のときと同じ
return NextResponse.next()
}
確認しておきたいのは、proxyに何を書いているかです。ログインしていない利用者をログイン画面に送る程度ならproxyで構いませんが、「このデータを見てよいか」の判定までproxyで済ませていると、Server Actionsやルートハンドラーから直接呼ばれたときに素通りします。認可の判定をどこに置くかは、社内システムの権限設計をNext.js 16でどう実装するかで、proxyとData Access Layerの役割分担として書いています。
更新後は、どこを確認する?
buildが通ったら、16で挙動が変わる箇所を画面で確かめます。自動テストがあれば、表の項目のうちテストで確かめられるものを先に流し、残りを手で確認します。
| 確認する画面・操作 | 見ること | 関係する変更 |
|---|
| 商品画像・記事のアイキャッチ | 画質が落ちていないか | images.qualities |
| 画像を差し替える管理画面 | 差し替えが反映されるまでの時間 | minimumCacheTTL |
| 一覧から詳細へ、詳細から戻る | スクロール位置と遷移の動き | scroll-behavior、ナビゲーションの変更 |
| ログイン前後、権限で表示が変わる画面 | リダイレクトと表示の切り替え | middleware → proxy |
| 投稿・更新したあとの一覧 | 更新した内容がすぐ出るか | revalidateTag の第2引数、updateTag |
| OG画像、サイトマップ | 生成されるか、URLが変わっていないか | params・id のPromise化 |
16では、next build の出力からページごとの size と First Load JS の表示がなくなりました。15のときにこの数字でバンドルの大きさを見ていた場合は、Lighthouseなどの計測に切り替えます。
投稿した内容を本人にすぐ見せたい画面では、revalidateTag(tag, 'max') だと古い内容が一度表示されます。Server Actionsの中なら updateTag を使うと、同じリクエストの中でキャッシュを更新できます。キャッシュの使い分けはNext.js 16 の use cache を中小企業のフルスクラッチでどう使い分けるかで詳しく扱っています。
自動テストがない場合は、上の表がそのまま手作業の確認項目になります。AIで書いたコードのテストをどこまで用意するかは中小企業のNext.jsフルスクラッチで、テストをどこまで書くかにまとめています。
まとめ
- Next.js 15のサポートは2026年10月21日で終わります。移行が間に合わない場合も、15.5.27への更新を先に済ませておきます
- 移行は「前提の確認 → 15.5.27 → codemod2つ → 残りの洗い出しと手直し → buildと画面確認」の順に進めます
- codemodのあとに残るのは、画像の既定値、独自のwebpack設定、Parallel Routesの
default.js、revalidateTag の引数、runtimeConfig、AMPなどです。記事内のスクリプトで一覧にできます
- buildが通っても、画質・キャッシュ時間・スクロールの動きは変わります。画面確認の項目に入れます
スクリプトの結果が多くて手が止まった場合や、どこまで確認すれば本番に出せるか判断がつかない場合は、ご相談ください。ゼットリンカーでは、Next.jsで動いているシステムのコードを読んで移行の作業範囲を洗い出し、手直しと検証、本番への反映までを保守として引き受けています。保守の契約で移行をどう扱うかは、Next.js保守運用の費用と契約で確認項目を整理しています。
※本記事は、2026年10月6日時点の Next.js公式の移行ガイド(16.3.8版)、Next.js Support Policy、vercel/next.js のReleases に基づいています。16.4以降のリリースで手順が変わることがあります。
技術仕様・対象バージョンは本文と参照先をご確認ください。
最終更新:2026年10月6日