Next.js v15 アップグレードで躓いたこと


こんにちは、Gaji-Labo フロントエンドエンジニアの下條です。

Next.js v15 へのアップグレードは、公式のアップグレードガイド や codemod による自動化ツールのおかげで基本的な手順自体はシンプルです。しかし、実際のプロジェクトで適用した際に、いくつか想定外のエラーに直面することがありました。本記事では、アップグレード時に遭遇した課題とその対処法について、いくつか紹介します。

始めに

Next.js v14 から v15 へのアップグレード時に遭遇した課題について紹介します。

アップグレード手順については、公式のアップグレードガイド や、「Next.js をバージョン14から15にアップデートした」記事を参照ください。

1. params/searchParams/cookies などの非同期API化による対応漏れ

Next.js v15 からは、paramssearchParamscookies などのAPIが非同期(Promise)に変更されました。

codemod では、これらのAPIを非同期化するための変換を行ってくれますが、移行が完全にされないパターンがありました。

また、何か事情があり codemod を使わず手動で変更を行う場合は、移行漏れが発生しやすいため注意が必要です。 非同期化の対応が漏れている箇所があっても、コード上でエラーは出ず、ビルドも通ってしまうため、なかなか発見しづらいです。

正常に移行できる例

// page.tsx
// 移行前
type Props = {
  params: { name: string };
  searchParams: { [key: string]: string | string[] | undefined };
};

export default function page(props: Props) {
  const { name } = props.params
  const { query } = props.searchParams

  return <SomeComponent name={name} query={query} />;
}

// 移行後
type Props = {
  params: Promise<{ name: string }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

export default async function page(props: Props) {
  const { name } = (await props.params);
  const { query } = (await props.searchParams);

  return <SomeComponent name={name} query={query} />;
}

正常に移行できないパターン1. Spread Attributes で Props を渡している

<SomeComponent {...props} /> のような(…)の形で、Props を渡している場合、codemod による変換はされないため、手動での変換が必要になります。

// page.tsx
// 移行前
type Props = {
  params: { name: string };
}

export default function Page(props: Props) {
  return <SomeComponent {...props} />; // 変換されない
}

正常に移行できないパターン2. page.tsx 内で 再エクスポートしている

コード例のように、page.tsx 外で 定義した generateMetadata などを、page.tsx 内で再エクスポートしている場合、codemod の変換はスキップされます。

// page.tsx
// 移行前
export { generateMetadata } from "@/some-component"; // 変換されない

// some-component.tsx
type Props = {
  params: { name: string };
};
export function generateMetadata({ params }: Props) {
  return {
    title: `${params.name} | Some Component`,
  };
}

ただし、パターン1,2 の場合でも、 codemod を実行した際に、変換がスキップされることは @next-codemod-error のコメントで明示されるため、漏れが発生しづらい仕組みになっています。

// page.tsx
// 移行前
export { generateMetadata } from "@/some-component";

// 移行後
export { /* @next-codemod-error `generateMetadata` export is re-exported. Check if this component uses `params` or `searchParams`*/
generateMetadata } from "@/some-component";

2. useSearchParams を使用しているコンポーネントでビルドエラーが発生するようになった

useSearchParams を内部で使っているクライアントコンポーネントを使用する場合、Suspense でラップしていないとビルドエラーになります。

useSearchParams は、Next.js のレンダリングプロセスに特殊な介入を行い、Suspense の境界を挟まないと、ページ全体がクライアントサイドでレンダリングの対象となります。そのため、Suspense の境界が存在しないとエラーになるというわけです。

(厳密にはこのエラーは Next.js v14 から存在していたものの、関わっていたプロジェクトでは v15 にアップデートしてからエラーが発生するようになりました)

参考記事: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout

'use client'
 
import { useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
 
function Search() {
  const searchParams = useSearchParams()
 
  return <input placeholder="Search..." />
}
 
// ❌ ビルドエラーになる
export function Searchbar() {
  return (
    <Search />
  )
}

// ✅ Suspense でラップする
export function Searchbar() {
  return (
    <Suspense>
      <Search />
    </Suspense>
  )
}

非推奨とされていますが、next.config.js でこのエラーを無視するように設定することも可能です。

// next.config.js
module.exports = {
  experimental: {
    missingSuspenseWithCSRBailout: false,
  },
}

3. エラー内容の更新により、これまでに出なかったエラーが出るようになった

Next.js v15 からは、エラー内容が更新されたため、これまでに出なかったエラーが出るようになったことがありました。いくつか例を紹介します。

非同期コンポーネントを Client Component で使用している

非同期コンポーネントを Client Component で使用している場合、v14 以前では Warning でしたが、 v15 からはエラーになるようになりました。

// async-component.tsx
export const AsyncComponent = async () => {
  const data = await fetchXXX();
  return <div>{data}</div>;
};

// client-component.tsx
// 非同期コンポーネントを Client Component で使用しているとエラーになる
"use client";
const ClientComponent = () => <AsyncComponent />;

console.error がポップアップとして表示されるようになった

Next.js v14 以前は、console.error は開発者ツールのコンソールにのみ表示されていましたが、v15 からはポップアップとして UI 上に表示されるようになりました。

この変更は、エラーに気づきやすくなり、デバッグしやすくなるため、通常は開発者にとってよい変更です。しかし、ライブラリで発生する避けられないエラーや、意図的に放置しているエラーがある場合、他のエラーのノイズとなり、ポップアップによるエラーを放置する習慣に繋がる可能性があります。

場合によっては、ライブラリのログのレベルを変更したり、局所的に console.errorconsole.warn に変更したりするような対応が必要になり得ます。

// 局所的に `console.error` を `console.warn` に変更する例
import("some-library").then((module) => {
  module.default({
    logger: (logToConsole) => {
      const logErrorFunc = console.error;
      console.error = console.warn;
      logToConsole();
      console.error = logErrorFunc;
    }
  });
});

まとめ

今回は、

  1. 非同期 API 化による対応漏れ
  2. useSearchParams を使用しているコンポーネントでビルドエラーが発生するようになった
  3. エラー内容の更新により、これまでに出なかったエラーが出るようになった

の3つについて紹介しました。

この記事が Next.js v15 へのアップグレードの参考になれば幸いです。

Gaji-Laboでは、 Next.js 経験が豊富なフロントエンドエンジニアを募集しています

弊社では Next.js の知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違う Gaji-Labo を味わいに来ませんか?

Next.js の設計・実装を得意とするフロントエンドエンジニア募集要項

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

タグ


投稿者 Takumi Shimojo

フロントエンドエンジニア。 通信会社でデザインシステムの構築や React / TypeScript の開発経験を経て、Gaji-Labo に参加。 Next.js / TailwindCSS / Chrome Extension / Figma が好きです。 デザイナーとエンジニアの両者の目線でプロダクトを作れる人材を目指しています。