stin's Blog

Next.js 15にアップデートした


このサイトで使っているNext.jsをv15にアップデートしました。

所詮ブログサイトなのでほとんど大きな変更はないですが、変更した内容を残しておきます。

内容

パッケージのアップデート

使っているNext.js関連のパッケージを一気にアップデートします。

npm i next@latest react@rc react-dom@rc @types/react@latest @types/react-dom@latest @next/bundle-analyzer@latest @next/third-parties@latest

注意点は、Next.js自体はv15が正式リリースされていますが、React v19はまだRC版です。なのでインストール時は @rc をつけてインストールします。

Async Request APIs

Next.js 15からpage.tsxlayout.tsxparamsPromiseで渡されるようになります。Async Request APIsと呼ばれている破壊的変更です。

このブログで例を挙げると、app/articles/[slug]/page.tsxはもともと次のようにslugパラメーターを取得できていました。

type Params = {
  slug: string;
};

const ArticlePage: React.FC<{ params: Params }> = async ({ params }) => {
  const { slug } = params;
  const article = getArticle(slug);

  if (!article) notFound();

  return <Article article={article} />;
};

Next.js 15からは、次のようにPromiseで渡されるため、awaitで取得する必要があります。

type Params = {
  slug: string;
};

const ArticlePage: React.FC<{
  params: Promise<Params>;
}> = async ({ params }) => {
  const { slug } = await params;
  const article = getArticle(slug);

  if (!article) notFound();

  return <Article article={article} />;
};

このブログでは2箇所だけAsync Request APIsを使っているので、それぞれ手で修正しました。codemodが公式提供されているため、大規模プロジェクトでもある程度自動で書き換えが可能です。

他にもこれまで同期関数やプレーンオブジェクトだったものが非同期関数やPromiseオブジェクトに変わっています。page.tsx, layout.tsxのコンポーネントだけでなく、route.tsopengraph-image.tsxgenerateMetadata関数なども対象です。

  • cookies()
  • headers()
  • draftMode()
  • params
  • searchParams

詳しくはこちら。

リクエストに依存しない部分はどんどん先にレンダリングして、本当にリクエストが必要な部分のレンダリングのときに初めてawaitするように変更していくという方針転換のようですね。

Client Router Cacheを復活させる

Next.js 15からClient Router Cacheが廃止されました。今までは一度訪問したページはしばらくNext.jsのrouterが記憶していて、再訪問してもページ取得リクエストしないようになっていましたが、この挙動がなくなります。同じページに訪問した回数だけリクエストが飛ぶようになるわけです。

ブログサイトの更新頻度は非常に少ないので、これまでのClient Router Cacheは維持されていてほしいところです。これはnext.configに新たに追加されたexperimental.staleTimes.dynamicにキャッシュタイムを指定することで以前の挙動と同じようにできます。

const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
    },
  },
};

export default nextConfig;

詳しくはこちら。

ESLint Flat Config移行

Next.jsのnext lintがESLint v9をサポートしたので、Flat Configを使うように変更しました。

初めてFlat Configの設定ファイルを書くので、TypeScript ESlintのサイトを参考に、eslint:recommendedとtypescript-eslint/recommendedを読み込みました。

続いて、Next.jsのESLintプラグインもいれるのですが、eslint-config-nextがFlat Configに対応していないので、次のGitHub Discussionを参考に、eslint-plugin-next、eslint-plugin-react、eslint-plugin-react-hooksを明示的に読み込むようにしました。

このブログサイトの完全なeslint設定ファイルは次のリンクを御覧ください。

Turbopackの使用は諦めた

Next.js 15から、開発ビルドでのTurbopackが使えるようになりました。

scripts: {
  "dev": "next dev --turbo",
}

しかし、このブログサイトではcontentlayerを使ってマークダウンファイルの処理をしていますが、これがWebpackに依存しており、Turbopackで動きませんでした。

ただ、途中までTurbopackを使おうと試していてわかったことを残しておきます。

Turbopackではnext.configのexperimental.typedRoutesがまだサポートされていないようです。なので一旦typedRoutesを無効にしました(というかなぜか型チェックできてなかった)。

Turbopackを有効化するときにsrc/app/favicon.icoでエラーが出ました。なぜか素のfaviconをビルドしようとして失敗しているようでした。GitHub Issueにすでに報告されていました。

favicon.icoをビルドが通らないpublicディレクトリに移動することで、とりあえず解決しました。src/app/favicon.icopublic/favicon.icoでは、レスポンス時のCache-Controlが次のように微妙に異なります。

  • src/app/favicon.ico: cache-control: public, max-age=0, must-revalidate
  • public/favicon.ico: Cache-Control: public, max-age=0

faviconのmust-revalidateの有無は大した問題ではないのでよいでしょう。

まとめ

このブログサイトのNext.jsをv15にアップデートしました。キャッシュ周りやAsync Request APIsなどの破壊的変更によるインパクトが大きいですね。でも、React Server ComponentsやApp Routerで最強になったNext.jsが大好きなので今後も使い続けます。

それでは良いNext.jsライフを!