メインコンテンツへスキップ
kt-tech.blog

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

実装7分で読めます

この記事でわかること

  • 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 を返すように変わった。プリレンダリング時とレンダリング時を分離するため。

ポイント
Next.js 14 までは同期的にアクセスできました。15 では一時的に同期アクセスも残されていましたが、16 では await が必須になっています。npx @next/codemod@latest upgrade で自動変換できます。

Before(Next.js 14 以前)

TypeScript
// src/app/blogs/[blogId]/page.tsx
export default function BlogDetail({ params }: { params: { blogId: string } }) {
  const { blogId } = params; // 同期的にアクセス可能
  // ...
}

After(Next.js 15+)

TypeScript
// 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引数も同様に変更

対応のポイント

TypeScript
// 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

TypeScript
// libs/gtag.ts のPageviewコンポーネント
'use client';
import { useSearchParams } from 'next/navigation';

export function GoogleAnalytics() {
  const searchParams = useSearchParams(); // Suspenseなしで使用
  // ...
}

After

TypeScript
// 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で囲む必要があった。

TypeScript
<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への更新が必要。

Bash
npm install @types/react@19 @types/react-dom@19

主な型の変更

TypeScript
// React.FC の children が暗黙的に含まれなくなった
// Before
const Component: React.FC = ({ children }) => { ... };

// After - 明示的にchildrenを定義
const Component: React.FC<{ children: React.ReactNode }> = ({ children }) => { ... };

useRef の変更

TypeScript
// 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

Tips
段階的にやるより、一気に上げたほうが楽なこともあります。13→14→15→16 と刻むと、中間バージョンごとに動作確認が必要になります。一気に上げればその確認が1回で済みます。
Tips
手で直す前に codemod を走らせる。npx @next/codemod@latest upgrade で、params の await 化など破壊的変更の大半が自動修正されます。残った分を TypeScript の型エラーで拾うのが楽なので、strict は入れておくと良いです。
注意
詰むのは Next.js 本体より周辺パッケージです。着手前に 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への更新で対応可能

参考リンク

更新履歴

  1. 前回の訂正が漏れていた箇所(「この記事でわかること」とまとめの「Next.js 14」)を 15 に修正。自動目次を追加。参考リンクの節を新設し、関連記事を追加。箸条書きだった「はじめに」を文章に整理し、「この記事でわかること」「対象読者」を概要プロパティへ移動。
  2. params の Promise 化を「Next.js 14」と誤記していたのを 15 に訂正(16 で必須化した経緯も追記)。Tips を整理

この記事のタグ