機能このページを編集.md

キャッシュ

@lazarv/react-serverは、レンダリングレスポンスのキャッシュ機構を提供しており、TTLや複合キャッシュキーのための組み込みのインメモリキャッシュを提供しています。

withCache ラッパーや useResponseCache フックを使用してサーバコンポーネントを使用する場合、レスポンスのキャッシュを有効にすることができます。ラップされたコンポーネントやキャッシュフックを使用したコンポーネントだけでなく、キャッシュが有効になっている HTTP レスポンス全体がキャッシュされます。

レスポンスキャッシュは、キャッシュプロバイダとHTTP Cache-Controlstale-while-revalidate付き)の両方を使用しています。サーバ側のキャッシュは、キャッシュが無効になるまでリクエストに使用されます。クライアント側のキャッシュは、同じクライアントからのリクエストに使用されます。

import { withCache } from "@lazarv/react-server"; export default withCache(async function App() { return <div>{Math.random()}</div>; }, 30 * 1000);
import { useResponseCache } from "@lazarv/react-server"; export default async function App() { useResponseCache(30 * 1000); return <div>{Math.random()}</div>; }

@lazarv/react-server から useCache ヘルパー関数をインポートすることで、インメモリキャッシュを使用することができます。このキャッシュを使用して、TTLと複合キャッシュキーを持つ非同期の値をキャッシュすることができます。キャッシュは全てのサーバコンポーネントで共有されます。

import { useCache } from "@lazarv/react-server"; import { readFile } from "node:fs/promises"; export default async function FileContent({ filename }) { const file = await useCache( ["file", filename], async () => readFile(filename, "utf-8"), 30 * 1000, ); return <pre>{file}</pre>; }

revalidate関数を使用すると、複合キーを使用してキャッシュを再検証することができます。この関数を呼び出すと、指定したキーのキャッシュが即座に無効になります。この関数はサーバコンポーネントでのみ使用できます。

import { revalidate } from "@lazarv/react-server"; export default async function App() { return ( <div> <FileContent filename="temp.txt" /> <form action={async () => { "use server"; revalidate(["file", filename]); redirect("/"); }} > <button type="submit">Refresh</button> </form> </div> ); }

"use cache" ディレクティブを使うと、どの関数でもキャッシュを有効にすることができます。このディレクティブは profilettltags オプションを受け付けます。profile オプションは使用するキャッシュプロファイルを指定するために使用します。ttl オプションはキャッシュの有効期間をミリ秒単位で指定するために使用し、キャッシュプロファイルの ttl オプションよりも優先されます。tags オプションはキャッシュキーのタグを指定するために使用します。

tags オプションを使用すると、特定のタググループのキャッシュを無効にするときに指定するタグのリストをカンマ区切りで指定することができます。例えば、todosを取得する関数があり、すべてのtodosのキャッシュを無効にしたい場合、tags オプションを使用して、todos タグをキャッシュキーに追加することができます。

App.jsx
import { invalidate } from "@lazarv/react-server"; async function getTodos() { "use cache; ttl=200; tags=todos"; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); return { timestamp: Date.now(), data: await res.json(), }; } export default async function App() { const todos = await getTodos(); return ( <form action={async () => { "use server"; await invalidate(getTodos); }} > <button type="submit">Refresh</button> <pre>{JSON.stringify(todos, null, 2)}</pre> </form> ); }

キャッシュプロファイルはサーバの設定で定義します。キャッシュプロファイルはいくつでも指定することができ、"use cache" ディレクティブの中で名前を指定して参照することができます。キャッシュプロファイルには ttltags オプションを含めることができ、"use cache" ディレクティブで指定しなかった場合に使用されます。

react-server.config.json
{ "cache": { "profiles": { "todos": { "ttl": 30000, "tags": "todos" } } } }

キャッシュプロファイルを定義した後は、"use cache" ディレクティブでその名前を参照することができます。

App.jsx
async function getTodos() { "use cache; profile=todos"; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); return { timestamp: Date.now(), data: await res.json(), }; }

キャッシュデータの保存に異なるキャッシュプロバイダを使用することができます。デフォルトのキャッシュプロバイダはインメモリキャッシュですが、ファイルベースのキャッシュやその他のカスタムキャッシュプロバイダも使用できます。

"use cache" ディレクティブで特定のキャッシュプロバイダを使用するには、"use cache: <provider>;" 構文でプロバイダを指定します。これにより、アプリケーションの異なる部分に異なるキャッシュプロバイダを使用することができます。

async function getTodos() { "use cache: file; tags=todos"; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); return { timestamp: Date.now(), data: await res.json(), }; }

@lazarv/react-serverUnstorage ライブラリを使用して、異なるストレージバックエンドに統一されたAPIを提供します。fslocalStoragememory、またはカスタムドライバなど、利用可能な任意のドライバを使用できます。

キャッシュプロバイダを使用すると、"use client" コンポーネントでも "use cache" ディレクティブを使用できます。これにより、クライアント側でデータをキャッシュし、クライアントコンポーネントで使用することができます。組み込みの local または session キャッシュプロバイダを使用してブラウザのローカルストレージやセッションストレージにキャッシュデータを保存することも、Unstorageの indexedb ドライバなど既存のカスタムキャッシュドライバを使用して別のストレージバックエンドにキャッシュデータを保存することもできます。

キャッシュプロバイダを定義するには、サーバ設定の cache.providers オプションを使用します。driver オプションはモジュールパスでドライバを指定し、options オプションはドライバのオプションを指定します。

export default { cache: { providers: { file: { driver: "unstorage/drivers/fs", options: { base: ".cache", }, } } }, };

プロバイダに type という特別なオプションを設定して、キャッシュプロバイダの種類を示すこともできます。これは @lazarv/react-server がキャッシュ値の処理方法を判断するために有用です。type"raw" に設定すると、キャッシュ値の保存時にUnstorageの setItemRaw を使用します。これはLRUなどのインメモリキャッシュドライバを使用する場合に、Reactツリーのようなメモリ構造を文字列にエンコードせずにキャッシュするのに便利です。

export default { cache: { providers: { file: { driver: "unstorage/drivers/lru", options: { type: "raw", }, } } }, };

cache.provider オプションを使用して、サーバのデフォルトキャッシュプロバイダを設定することもできます。"use cache" ディレクティブで特定のキャッシュプロバイダが指定されていない場合に使用されます。

export default { cache: { provider: { default: "unstorage/drivers/lru", }, }, };

サーバ設定でキャッシュプロバイダのエイリアスを指定することもできます。これにより、"use cache" ディレクティブで使用されるエイリアスに基づいて、キャッシュリクエストを異なるプロバイダにルーティングできます。特定のエイリアスのデフォルトキャッシュプロバイダをオーバーライドすることも可能です。

export default { cache: { provider: { default: "lru", lru: "unstorage/drivers/lru", }, }, };

キャッシュプロバイダのエイリアスを使用すると、アプリケーションのコードを変更せずに異なるキャッシュプロバイダを簡単に切り替えることができます。これにより、インメモリキャッシュ、ファイルベースのキャッシュ、その他のカスタムキャッシュソリューションなど、異なるキャッシュ戦略を、キャッシュを使用するコードやキャッシュプロバイダの設定を変更せずに切り替えることができます。

export default { cache: { provider: { default: "smallLRU", smallLRU: { driver: "unstorage/drivers/lru", options: { maxSize: 1000, // このエイリアスには小さいサイズを設定 type: "raw", }, }, largeLRU: { driver: "unstorage/drivers/lru", options: { maxSize: 10000, // このエイリアスには大きいサイズを設定 type: "raw", }, }, }, }, };

@lazarv/react-server は、設定なしですぐに使用できるいくつかの組み込みキャッシュプロバイダを提供しています:

request キャッシュプロバイダは、単一のHTTPリクエスト内で関数呼び出しを重複排除します。関数に "use cache: request" を付けると、その関数本体はリクエストごとに一度だけ実行されます。同じ引数での後続の呼び出しはすべて同じキャッシュ結果を返します。これはRSCとSSRのレンダリング環境をまたいで機能します。

これは、同じページレンダリング内で複数のコンポーネントが依存する高コストな計算やデータ取得に有用です。

リクエストキャッシュ関数の定義

任意の関数の先頭に "use cache: request" ディレクティブを使用します。関数は非同期にすることができ、Date オブジェクト、ネストされたオブジェクト、配列など、RSCシリアライズ可能な任意の値を返すことができます。

get-request-data.mjs
let computeCount = 0; export async function getRequestData() { "use cache: request"; // 非同期操作のシミュレーション await new Promise((resolve) => setTimeout(resolve, 5)); computeCount++; return { timestamp: Date.now(), random: Math.random(), computeCount, createdAt: new Date(), }; }

この例では、getRequestData はリクエストごとに一度だけ実行されます。同じリクエスト中にこの関数を呼び出すすべてのコンポーネントは、同一の timestamprandomcomputeCount の値を受け取ります。

サーバコンポーネントでの使用

サーバコンポーネントはリクエストキャッシュ関数を直接 await できます。同じ関数を呼び出す複数のサーバコンポーネントは結果を共有します。

App.jsx
import { getRequestData } from "./get-request-data.mjs"; async function First() { const data = await getRequestData(); return <div id="first">{JSON.stringify(data)}</div>; } async function Second() { const data = await getRequestData(); return <div id="second">{JSON.stringify(data)}</div>; } export default async function App() { return ( <div> <First /> <Second /> </div> ); }

<First /><Second /> は同じデータをレンダリングします — 関数本体は一度だけ実行されます。

クライアントコンポーネントでの使用

クライアントコンポーネントもReactの use フックを使用してリクエストキャッシュ関数を利用できます。キャッシュ値はRSCとSSR環境間で共有されるため、クライアントコンポーネントはサーバ上で既に計算された同じ結果を受け取ります。

ClientDisplay.jsx
"use client"; import { use } from "react"; import { getRequestData } from "./get-request-data.mjs"; export default function ClientDisplay() { const data = use(getRequestData()); return ( <div id="client"> <div>{data.timestamp}</div> <div>{data.random}</div> </div> ); }

ハイドレーション

デフォルトでは、リクエストキャッシュされた値は自動的にHTMLレスポンスにデハイドレートされ、Reactハイドレーション時にブラウザでリハイドレートされます。これにより、use() を介してリクエストキャッシュ関数を利用するクライアントコンポーネントは、サーバ上で計算されたのとまったく同じ値を受け取ります — 再計算なし、ハイドレーションミスマッチなし。

キャッシュされた値は、ReactのRSC Flightプロトコルを使用してシリアライズされ、DateMapSetRegExpURL などすべてのRSCサポート型が保持されます。シリアライズされたデータはHTMLストリームの末尾にインラインの <script> タグとして埋め込まれます。

ハイドレーションの無効化

キャッシュされた値をブラウザに送信するHTMLに公開したくない場合があります。例えば、キャッシュデータに機密情報が含まれている場合や、クライアントコンポーネントがデータを取得する独自の戦略を持っている場合です。hydrate=false オプションまたは no-hydrate フラグで自動ハイドレーションを無効にできます:

get-sensitive-data.mjs
export async function getSensitiveData() { "use cache: request; hydrate=false"; // または同等の構文: // "use cache: request; no-hydrate"; // この値はHTMLに埋め込まれません const data = await fetchInternalAPI(); return { publicField: data.publicField, internalMetric: data.internalMetric, }; }

hydrate=false(または no-hydrate)が設定されている場合:

仕組み

他のキャッシュプロバイダとは異なり、request プロバイダは ttltags オプションをサポートしません。キャッシュは本質的に単一のリクエストライフサイクルにスコープされているためです。

インメモリでないキャッシュプロバイダにReactコンポーネントを保存する場合、コンポーネントをシリアライズする必要があります。RSCフォーマットを使用してコンポーネントの状態を保存することができます。キャッシュプロバイダでRSCシリアライゼーションを使用するには、キャッシュプロバイダの設定で type オプションを rsc に設定します。これにより、キャッシュにコンポーネントを保存する際にRSCシリアライゼーションフォーマットが使用されます。

export default { cache: { providers: { file: { driver: "unstorage/drivers/fs", options: { base: ".cache", type: "rsc", }, } } }, };

保存されるRSCデータのエンコーディングも指定できます。デフォルトは base64 です。utf8hexbinary など、標準的なNode.jsのバッファエンコーディングを設定できます。これはストレージバックエンドと互換性のある特定の形式でRSCデータを保存する場合に便利です。エンコーディングを指定するには、type オプションを rsc;<encoding> に設定するか、キャッシュプロバイダ設定の encoding オプションを使用します。キャッシュドライバが独自の encoding オプションをサポートしている場合は、type オプションで encoding を指定することで、キャッシュドライバの encoding オプションを使用することもできます。

export default { cache: { providers: { file: { driver: "unstorage/drivers/fs", options: { base: ".cache", type: "rsc;utf8", // または type: "rsc", encoding: "utf8", }, } } }, };

RSCシリアライザは独自の用途にも利用できます。@lazarv/react-server/rsc モジュールを使用して、Reactツリーをバッファやストリームに変換したり、戻したりすることができます。バッファは Uint8Array インスタンスで、ストリームは ReadableStream インスタンスです。toBufferfromBuffer 関数でReactツリーをバッファに変換したり戻したり、toStreamfromStream 関数でReactツリーをストリームに変換したり戻したりできます。

import { toBuffer, fromBuffer } from "@lazarv/react-server/rsc"; const buffer = await toBuffer(<div>Hello world</div>); const tree = await fromBuffer(buffer); const stream = await toStream(<div>Hello world</div>); const tree = await fromStream(stream);

RSCシリアライゼーションは現在サーバコンポーネントでのみ利用可能ですが、将来的にはブラウザ環境でも利用可能になる可能性があります。アップデートにご期待ください!