SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine(コードジン) DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Next.jsがApp Routerの"次"に目指すもの

Next.js 15の新機能――Next.jsが次に見据えるキャッシュ制御

Next.jsがApp Routerの"次"に目指すもの 第3回

"use cache" の利用パターン

 "use cache"は、宣言するスコープに応じて、少しずつ挙動が異なります。大きく分けて、次の3パターンがあります。

  1. コンポーネント関数をキャッシュ対象にする
  2. 通常の関数をキャッシュ対象にする
  3. ファイル全体をキャッシュ対象にする

 ひとつずつ見ていきましょう。

1. コンポーネント関数をキャッシュ対象にする

 まずはコンポーネント関数をキャッシュ対象にするパターンです(リスト3)。

[リスト3]app/page.js
export default function Home() { // (4)
  return (
    <div>
      <main>
        <Uuid />{/* (4) */}
      </main>
    </div>
  );
}

async function Uuid() { // (1)
  "use cache"; // (2)

  // (3)
  const data = await fetch("https://httpbin.org/uuid")
    .then(res => res.json());

  return (
    <div>
      <p>UUID: {data.uuid}</p>
    </div>
  )
}

 (1)でUuidという名前の非同期コンポーネントを作成しました。このコンポーネントをキャッシュ対象にするために、関数の先頭に(2)のように"use cache"を宣言しています。ちなみに、"use cache"を指定できる対象は非同期関数だけで、同期関数に指定するとエラーになります。

 このコンポーネントは内部で(3)のようにランダムなUUIDを取得するWeb APIを呼び出しており、キャッシュなしで動かす場合は実行するたびに違ったUUIDを取得・表示します。キャッシュが効いた場合は、UUIDが変わらないことを期待したいところです。では、実際に動かしてみましょう(図2)。

図2:コンポーネント関数をキャッシュ対象にする
図2:コンポーネント関数をキャッシュ対象にする

 UUIDが表示されました。リロードしてみると、期待通り、UUIDが変わらないことがわかります。このパターンでは、<Uuid>コンポーネントが親である<Home>コンポーネントとまとめてStatic Renderingの対象になるため、ビルド時にキャッシュが生成されます。

2. 通常の関数をキャッシュ対象にする

 次は、通常の関数をキャッシュ対象にするパターンです(リスト4)。

[リスト4]app/page.js
export default async function Home() {
  console.log("Home"); // (5)

  // (4)
  const base64str1 = "aGVsbG8="; // hello
  const base64str2 = "Z29vZGJ5ZQ=="; // goodbye
  const text1 = await decodeBase64(base64str1);
  const text2 = await decodeBase64(base64str2);
  const text3 = await decodeBase64(base64str1);
  const text4 = await decodeBase64(base64str2);

  return (
    <div>
      <main>
        <p>{base64str1} → {text1}</p>
        <p>{base64str2} → {text2}</p>
        <p>{base64str1} → {text3}</p>
        <p>{base64str2} → {text4}</p>
      </main>
    </div>
  );
}

// (1)
async function decodeBase64(base64str) {
  "use cache"; // (2)
  console.log("decodeBase64", base64str); // (6)

  // (3)
  const text = await fetch(`https://httpbin.org/base64/${base64str}`)
    .then(res => res.text());

  return text;
}

 今度は、(1)で定義したdecodeBase64()関数をキャッシュ対象にするために、関数の先頭で(2)のように"use cache"を宣言しています。この関数は(3)でWeb APIを呼び出してBase64でエンコードされた文字列をデコードする機能があります。

 この関数を、(4)のように<Home>コンポーネントの中で2種類のBase64文字列を2回ずつ呼び出してみました。Base64デコードの結果は何回やっても変わらないので、それぞれ2回目の呼び出し結果にはキャッシュが使われていると嬉しいところです。

 では、実際に動かしてみましょう(図3)。

図3:通常の関数をキャッシュ対象にする
図3:通常の関数をキャッシュ対象にする

 2回ずつ出力されていますが、これだけでは期待通りなのかどうか、まだわかりません。そこで、(5)と(6)に仕込んだconsole.log()の出力を見てみましょう(図4)。

図4:関数が1回ずつだけ呼び出されている
図4:関数が1回ずつだけ呼び出されている

 1回のリロードで<Home>コンポーネントが3回評価されているので少し長めですが、まとまりとしては、Home関数が1回呼び出されるごとに、decodeBase64()関数が1回ずつだけ実行されていることがわかります。2回目の実行時にはキャッシュが使われるよう、コンパイル時に置き換えられるので、 decodeBase64()関数は呼び出されません。

 また、引数ごとに別々のキャッシュが使われているのもうれしいポイントですね。

3. ファイル全体をキャッシュ対象にする

 最後は、ファイル全体をキャッシュ対象にするパターンです(リスト5)。

[リスト5]app/uuid.js
"use cache"; // (1)

export async function getUuid() {
  console.log("getUuid");
  const data = await fetch("https://httpbin.org/uuid")
    .then(res => res.json());
  return data.uuid;
}

 リスト3で使っていたUUID生成のWeb APIを関数として切り出して、別ファイルに定義しました。(1)でファイル自体に"use cache"を宣言しているので、このファイルからexportした関数はすべてキャッシュ対象になります。では、実際に画面に組み込んでみます(リスト6)。

[リスト6]app/page.js
import { getUuid } from "./uuid";

export default async function Home() {
  console.log("Home");

  const uuid1 = await getUuid();
  const uuid2 = await getUuid();

  return (
    <div>
      <main>
        <p>{uuid1}</p>
        <p>{uuid2}</p>
      </main>
    </div>
  );
}

 リスト4と同様に、複数回呼び出して様子を見ます。では、実行してみましょう(図5)。

図5:ファイル全体をキャッシュ対象にする
図5:ファイル全体をキャッシュ対象にする

 同じ値が続いているので、キャッシュが効いていそうですね。ログも見てみましょう(図6)。

図6:ログを見てみる
図6:ログを見てみる

 <Home>コンポーネントが3回評価されているのは図4と同様ですが、getUuid()関数は1回だけ呼び出されている点が異なります。これは、ビルド時にファイルごとにキャッシュが生成されているためです。

 リスト4は、app/page.jsのリロードと同じタイミングでdecodeBase64()関数のキャッシュも破棄されていました。一方、リスト6のapp/uuid.jsは、開発サーバーの起動時のビルドで1回だけキャッシュが生成され、その後はapp/page.jsのリロードの影響を受けないため、キャッシュが破棄されることはありません。そのため、このような挙動になっています。これは"use cache"の指定先が関数かファイルかという違いではなく、コンポーネントとは別のファイルに関数を定義している効果ですね。

 そしてこのケースでも、<Home>コンポーネント内はキャッシュされたコンテンツだけで構築されているので、ページ全体としてはStatic Renderingの対象になっています。あまり更新しなくていいコンテンツには"use cache"を指定して、最新情報を提供するコンテンツは<Suspense>内に置いてPartial Prerenderingを行う、といった使い分けができると、かなりスッキリしたコードになるのではないでしょうか。

次のページ
"use cache" はパラメータの扱いが賢い

この記事は参考になりましたか?

Next.jsがApp Routerの"次"に目指すもの連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川 幸哉(ナカガワ ユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/21221 2025/04/02 11:00

おすすめ

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー