Next.js 16 アップデート:脆弱性 0 への道とビルドエラーの回避
Next.js の公式チュートリアルをベースにしたダッシュボードアプリにおいて、セキュリティ脆弱性の解消をきっかけに Next.js 16 へのアップデートを敢行しました。
最新の React 19 や Next.js 16 では、これまでの書き方が通用しない「破壊的変更」や、ビルド時の挙動の変化がいくつかあります。フリーランスエンジニアとして、実務でも遭遇しそうなこれらのトラブルをどう解決したか、その全記録を共有します。
1. 脆弱性 0 件へ:パッケージの強制アップデート
Section titled “1. 脆弱性 0 件へ:パッケージの強制アップデート”まず着手したのは、pnpm audit で指摘された 16 件の脆弱性対応です。
古いチュートリアルコードでは依存ライブラリが固定されていることが多く、単純な pnpm update では解消しきれない場合があります。今回は package.json の pnpm.overrides を活用し、安全なバージョンを強制的に指定することで、脆弱性を 0 件まで抑え込みました。
2. React 19 の洗礼:Server Actions の型定義
Section titled “2. React 19 の洗礼:Server Actions の型定義”Next.js 16(React 19)に上げた直後、TypeScript のビルドエラーが発生しました。
発生したエラー
Section titled “発生したエラー”./app/ui/invoices/edit-form.tsx などの form action 属性において、以下の型不一致が起きました。
Type
Promise<{ message: string; }>is not assignable to typevoid | Promise<void>.
React 19 から、form action に渡す関数は戻り値が void または Promise<void> であることが厳格に求められるようになりました。従来のコードでオブジェクト(メッセージなど)を返している場合、エラーになります。
これを解決するために、以下のようにラッパー関数で囲む修正を行いました。
// 修正前<form action={updateInvoiceWithId}>
// 修正後(ラッパーで囲んで戻り値を無視させる)<form action={async (formData) => { await updateInvoiceWithId(formData); }}>3. ビルドの壁:Neon DB 接続エラーと PPR
Section titled “3. ビルドの壁:Neon DB 接続エラーと PPR”一番苦労したのが、pnpm run build 時のデータベース接続エラーです。
ビルド中に Neon DB へ接続しようとして HANGING_PROMISE_REJECTION が発生。WSL2(GALLERIA)のローカル環境から外部 DB への接続が、プリレンダリング工程でタイムアウトしてしまうのが原因でした。
解決策:force-dynamic の活用
Section titled “解決策:force-dynamic の活用”Next.js 16 の cacheComponents (Partial Prerendering) を有効にしていると、ビルド時に DB を叩きに行こうとします。今回はビルドを確実に成功させるため、一旦 app/layout.tsx に以下の設定を投入しました。
export const dynamic = 'force-dynamic';これにより、「ビルド時に静的にページを作る」のをやめ、「リクエスト時に動的に生成する」挙動に強制変更。ビルド時のネットワーク起因によるエラーを完全に回避できました。
※注意:cacheComponents が有効だと dynamic = 'force-dynamic' はエラーになるため、ビルドを優先する場合は next.config.mjs で一旦 PPR をオフにする調整も必要です。
今回のアップデートを通じて、以下の 3 点が重要だと再認識しました。
-
脆弱性対応は定期的に:
overridesを駆使してでも環境を清潔に保つ。 -
型定義の変化に敏感に: React 19 の
form actionなどの仕様変更を把握する。 -
環境差分を考慮する: ローカル(WSL2)とデプロイ先(Vercel)でビルド時の挙動が異なる場合、
force-dynamicなどのフラグを戦略的に使う。
次は、Vercel 上で本来のパフォーマンスを発揮させるための「PPR 戻し」に挑戦する予定です。