🐃
Next.js 15のuse cahceディレクティブの、今までのキャッシュと違う点

mokun632

mokun632

2024年11月6日
Next.js

Next.js 15のuse cahceディレクティブについて、紹介し、今までのキャッシュと何が違うかについて、解説します。

# use cahce ディレクティブとは?

use cahce は、use serveruse clientのように Next.js 15 で導入された新しいディレクティブです。
use serveruse clientは、React のネイティブの機能であり、use cacheは Next.js の機能になります。

use cahce は、コンポーネントや関数をキャッシュするために使用されます。
ファイル自体をキャッシュする場合は、ファイルの先頭に、関数やコンポーネント単位でキャッシュする場合は、インラインに記述します。

// File level
"use cache";

export default async function Page() {
  // ...
}

// Component level
export async function MyComponent() {
  "use cache";
  return <></>;
}

// Function level
export async function getData() {
  "use cache";
  const data = await fetch("/api/data");
  return data;
}

2024 年 11 月現在では experimental な機能であり、下記のようにnext.config.jsに設定を追加することで有効になります。

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    dynamicIO: true,
  },
};

export default nextConfig;

# キャッシュの挙動

dynamicIOフラグを有効にした状態で、fetchをしたり、Async Request APIを使用すると、静的解析され、エラーが発生します。

export async function Page() {
  const data = await fetch("/api/data");
  return <div>{data}</div>;
}

[ Server ] Error: Route "/": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info:

ですので、もし、動的なデータを取得する場合は、Suspenseの使用が強制されます。

export async function DynamicComponent() {
  const data = await fetch("/api/data");
  return <div>{data}</div>;
}

export async function Page() {
  const data = await fetch("/api/data");
  const todos = await data.json();
  return (
    <Suspense fallback={<>...Loading</>}>
      <DynamicComponent />
    </Suspense>
  );
}

fetchしてくるデータをキャッシュしたい場合は、use cacheを使用します。

export async function CacheComponent() {
  "use cache";
  const data = await fetch("/api/data");
  return <div>{data}</div>;
}

export async function Page() {
  return <CahceComponent />;
}

また、use cache内で,Async Request APIを使用しても、下記エラーが発生します。

"use cache";

import { cookies } from "next/headers";

export default async function Page() {
  const data = await fetch("/api/data");
  const todos = (await data.json()) as { id: number; title: string }[];
  const cookieStore = await cookies();
  cookieStore.get("token");
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

[ Server ] Error: Route / used "cookies" inside "use cache". Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use "cookies" outside of the cached function and pass the required dynamic data in as an argument. See more info here:

# 今までのキャッシュとの違い

他にもuse cacheは revalidate できたり、tag を指定できたりするのですが、一旦、use cacheの説明はここまでにして、今までのキャッシュとの違いを解説します。

# キャッシュキーが暗黙的に生成される

use cacheは、関数の場合、引数、コンポーネントの場合は、props がキャッシュキーとして暗黙的に生成されます。
そのため、キャッシュキーの管理が不要になります。
例えば、unstable_cacheの場合のキャッシュキーは以下のようになります。

const getCachedUser = unstable_cache(
  async () => {
    return { id: params.userId };
  },
  [params.userId] // キャッシュキー
);

unstable_cacheの場合でも、第一引数の関数自体と、引数がキャッシュとして自動で生成されますが、関数に引数がない場合は、キャッシュキーを指定する必要があります。
またそれ以外の場合でも、自由に任意にキャッシュキーを追加する事ができます。
use cacheの場合は、キャッシュキーが不要なだけでなく、任意でキーを追加する事もできません。
その代わり、シリアライザブルな引数、props でないといけないので、その点は注意が必要です。
また、返り値もシリアライザブルでないといけないです。(JSX を除く)

# キャッシュする領域を境界線として定義できる

Suspenseは React の core チームなどからは、Suspense Boundaryと呼ばれていました。
また、use clientも境界線として、意味合いがあります。
use cacheも、Suspenseuse clientと同じようにキャッシュする領域を境界線として定義できます。
これは今までのソフトウェアのアーキテクチャにはなかったのではないかと思います。
また、境界線とはちょっと違いますが、use serverとも似ていると言われてします。
use cacheもインラインで宣言でき、server action のように呼び出せるからです。
ただ、server action と違い呼び出されるのはcache(function or component)です。

# React のベストプラクティスを半強制できる

先ほど、dynamicIOフラグを有効にした状態で、async componentuse cacheSuspenseもなしで、レンダリングすると、エラーが発生しました。
これは、動的なコンポーネントであれば、Suspenseの使用を強制でき、そうでなければ、use cacheを使用し、キャッシュすることを強制できます。