【実践】Next.js 13→16メジャーアップグレードの全記録 — 破壊的変更と対応策

この記事でわかること
- Next.js 15 で params が Promise になった変更と、全動的ルートへの影響
- useSearchParams に Suspense 境界が必須になった件の対処(GA の共通コンポーネントが詰まる)
- 3世代スキップで一気に上げるときの進め方と、実測したビルド速度の変化
Next.js App Router のプロジェクト。この記事は 13.4 → 16.1 へのアップグレード記録です
概要: Next.js 13.4から16.1へのメジャーアップグレードで遭遇した破壊的変更と対応策をまとめました。params型のPromise化、useSearchParamsのSuspense必須化、Turbopack標準化など、各バージョンの重要な変更点を解説します。
はじめに
Next.js 13 から 16 へ、3 世代スキップでアップグレードしました。きっかけは Cloudflare Workers への移行で、OpenNext が Next.js 15 以上を要求したことです。段階的にやるか一気にやるか迷いましたが、各バージョンの破壊的変更を先に洗い出してから一気に上げる方針を選びました。
1. Next.js 15: params型がPromiseに変更
変更内容
Next.js 15 で、動的ルートの params をはじめとするリクエスト依存の API(params / searchParams / cookies() / headers() / draftMode())が Promise を返すように変わった。プリレンダリング時とレンダリング時を分離するため。
await が必須になっています。npx @next/codemod@latest upgrade で自動変換できます。Before(Next.js 14 以前)
// src/app/blogs/[blogId]/page.tsx
export default function BlogDetail({ params }: { params: { blogId: string } }) {
const { blogId } = params; // 同期的にアクセス可能
// ...
}After(Next.js 15+)
// src/app/blogs/[blogId]/page.tsx
export default async function BlogDetail({ params }: { params: Promise<{ blogId: string }> }) {
const { blogId } = await params; // awaitが必須
// ...
}影響範囲
[blogId]/page.tsx— ブログ詳細ページ[categoryId]/page.tsx— カテゴリ別一覧ページ[tagId]/page.tsx— タグ別一覧ページpage/[pageId]/page.tsx— ページネーションgenerateMetadata関数のparams引数も同様に変更
対応のポイント
// generateMetadataも同様にawaitが必要
export async function generateMetadata({ params }: { params: Promise<{ blogId: string }> }) {
const { blogId } = await params;
const blog = await getDetail(blogId);
return { title: blog.title };
}2. Next.js 15: useSearchParamsにSuspense境界必須
変更内容
Next.js 15では、useSearchParams()を使用するコンポーネントを<Suspense>で囲むことが必須になった。囲まないとビルドエラーが発生する。
問題が発生したコンポーネント
Google Analyticsのページビュー追跡でuseSearchParamsを使用していたため、ビルドが失敗した。
Before
// libs/gtag.ts のPageviewコンポーネント
'use client';
import { useSearchParams } from 'next/navigation';
export function GoogleAnalytics() {
const searchParams = useSearchParams(); // Suspenseなしで使用
// ...
}After
// layout.tsx
import { Suspense } from 'react';
import { GoogleAnalytics } from '@/libs/gtag';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Suspense fallback={null}>
<GoogleAnalytics />
</Suspense>
{children}
</body>
</html>
);
}検索ページでの対応
検索フォームコンポーネントも同様にSuspenseで囲む必要があった。
<Suspense fallback={<SearchFormSkeleton />}>
<SearchForm />
</Suspense>3. Next.js 16: Turbopack標準化
変更内容
Next.js 16ではTurbopackがデフォルトのバンドラーとして標準化された。
ビルド速度の改善
| 指標 | webpack(Next.js 13) | Turbopack(Next.js 16) |
|---|---|---|
| dev起動 | 約3.5秒 | 約1.2秒 |
| HMR | 約800ms | 約200ms |
| プロダクションビルド | 約45秒 | 約30秒 |
注意点
next.config.jsのwebpack設定はTurbopackでは無視される- カスタムwebpackプラグインを使用している場合は移行が必要
- 今回のプロジェクトではwebpackのカスタム設定がなかったため影響なし
4. React 18→19の変更点
型定義の更新
React 19では型定義が大幅に変更された。@types/react@19への更新が必要。
npm install @types/react@19 @types/react-dom@19主な型の変更
// React.FC の children が暗黙的に含まれなくなった
// Before
const Component: React.FC = ({ children }) => { ... };
// After - 明示的にchildrenを定義
const Component: React.FC<{ children: React.ReactNode }> = ({ children }) => { ... };useRef の変更
// Before: initialValueがnullでもOK
const ref = useRef<HTMLDivElement>(null);
// After: React 19では RefObject の型が変更
// null初期値の場合は MutableRefObject ではなく RefObject を返す
const ref = useRef<HTMLDivElement>(null);
// 型は RefObject<HTMLDivElement | null> になるTips
npx @next/codemod@latest upgrade で、params の await 化など破壊的変更の大半が自動修正されます。残った分を TypeScript の型エラーで拾うのが楽なので、strict は入れておくと良いです。npm outdated で全依存を見て、Next.js の新バージョンに追従していないパッケージを先に洗い出しておくと、途中で止まらずに済みます。まとめ
- Next.js 13→16の3世代スキップアップグレードは計画的に行えば十分実用的
- 最大の破壊的変更は params 型の Promise 化(Next.js 15 で導入、16 で必須化)で、全動的ルートの修正が必要
- useSearchParams の Suspense 必須化(Next.js 15)は Google Analytics 等の共通コンポーネントに影響するので、サイト全体のビルドが止まる
- Turbopack標準化(Next.js 16)は基本的にゼロコストで恩恵を受けられる
- React 19の型変更は
@types/react@19への更新で対応可能
参考リンク
更新履歴
- 前回の訂正が漏れていた箇所(「この記事でわかること」とまとめの「Next.js 14」)を 15 に修正。自動目次を追加。参考リンクの節を新設し、関連記事を追加。箸条書きだった「はじめに」を文章に整理し、「この記事でわかること」「対象読者」を概要プロパティへ移動。
- params の Promise 化を「Next.js 14」と誤記していたのを 15 に訂正(16 で必須化した経緯も追記)。Tips を整理


