react-server675fbba4
react-servertreemaindocssrcpagesja(pages)routerresources.mdx
docs/src/pages/ja/(pages)/router/resources.mdxmdx34.7 KiBf14dab03

title: リソース category: Router order: 13

import Link from "../../../../components/Link.jsx";

リソース

@lazarv/react-serverには、スキーマバリデーション済みでルート対応のデータ取得のための型付きリソースレイヤーが含まれています。リソースは型付きルーターと同じ原則に基づいています — ディスクリプタが形状を定義し、実装が動作をバインドし、すべてがエンドツーエンドで型安全です。

リソースはキャッシュランタイムとして"use cache"を使用します。カスタムキャッシュレイヤー、SWRロジック、TTL設定はありません。キャッシュが必要な場合は、ローダー関数に"use cache"を追加します。クライアントでは、"use cache"はUnstorageエンジンを介してブラウザストレージプロバイダー — sessionStorage、localStorage、IndexedDB、インメモリ — またはカスタムプロバイダーをサポートします。

<Link name="create-resource"> ## createResourceでリソースを定義する </Link>

createResourceを使用してリソースディスクリプタを定義します。ディスクリプタはキースキーマ(ルックアップキーの形状)を持ちますが、実装は含みません。クライアントセーフです — サーバーとクライアントの両方のコンポーネントからインポートできます。

import { createResource } from "@lazarv/react-server/resources";
import { z } from "zod";

// キー付きリソース — Zodでバリデーション
export const userById = createResource({
  key: z.object({ id: z.coerce.number().int().positive() }),
});

// シングルトンリソース — キーなし
export const currentUser = createResource();

// 軽量パース — スキーマライブラリ不要
export const postBySlug = createResource({
  key: { slug: String },
});

// 複合キー
export const posts = createResource({
  key: z.object({
    page: z.coerce.number().default(1),
    tag: z.string().optional(),
  }),
});

キースキーマはルートパラメータと同じバリデーション戦略をサポートします:

戦略 ライブラリ
スキーマバリデーション z.object({ id: z.coerce.number() }) Zod, ArkType, Valibot
軽量パース { id: Number } なし(組み込みコンストラクタ)
キーなし(シングルトン) createResource() なし
<Link name="bind-loader"> ## ローダーのバインド </Link>

.bind(loaderFn)を使用してローダー関数をディスクリプタにバインドします。ローダーは単なる非同期関数です — キャッシュには本体に"use cache"を追加します。.bind()はディスクリプタを変更し、TData型がローダーの戻り値の型に絞り込まれた状態で返します — 型付き.use()呼び出しのために戻り値をキャプチャしてください。ローダーはサーバーまたはクライアントに配置できます。

サーバーサイドローダー

シンプルなリソースの場合、ディスクリプタの定義とローダーのバインドを1つのファイルで行います:

import { createResource } from "@lazarv/react-server/resources";
import { z } from "zod";

export const userById = createResource({
  key: z.object({ id: z.coerce.number().int().positive() }),
}).bind(async ({ id }) => {
  "use cache";
  return db.users.findById(id);
});

export const currentUser = createResource().bind(async () => {
  return (await getSession()).user;
});
import { createResource } from "@lazarv/react-server/resources";

export const postBySlug = createResource({
  key: { slug: String },
}).bind(async ({ slug }) => {
  "use cache";
  return db.posts.findBySlug(slug);
});

クライアントサイドローダー

ローダーはクライアントにも配置できます — "use cache"はサポートされるストレージプロバイダー(sessionStorage、localStorage、IndexedDB、インメモリ、またはカスタムUnstorageドライバー)を使用してブラウザでキャッシュします。サーバーへのラウンドトリップは不要です。

ローダーの実装は抽象的です — fetch()呼び出し、WebSocketメッセージ、IndexedDBストアからの読み取り、派生データの計算など、任意の非同期関数にできます。サーバーコードがクライアントバンドルに到達しないように、モジュールに"use client"を付けます。

"use client";
import { createResource } from "@lazarv/react-server/resources";

export const searchResults = createResource({
  key: { q: String },
}).bind(async ({ q }) => {
  "use cache";
  const res = await fetch(`/api/search?q=${q}`);
  return res.json();
});
<Link name="client-only-routes"> ### リソースを使ったクライアント専用ルート </Link>

ブラウザで完全にデータをロードするルートの場合、ディレクトリ構造を持つデュアルローダーパターンを使用します:共有ディスクリプタ、サーバーローダー、クライアントローダー。

共有ディスクリプタがリソースのアイデンティティを保持します。サーバーとクライアントの両方のローダーがこれにバインドし、モジュール境界をまたいだSSRハイドレーションが機能するようにします。クライアントモジュールには"use client"を付けて、サーバーコードがクライアントバンドルに到達しないようにします。

// 共有ディスクリプタ — サーバーとクライアントの両方のローダーがバインド
import { createResource } from "@lazarv/react-server/resources";

export const todos = createResource({
  key: { filter: String },
});
import { todos as resource } from "./resource";

export const todos = resource.bind(async ({ filter }) => {
  "use cache: request";
  return db.todos.list({ filter });
});
"use client";
import { todos as resource } from "./resource";

export const todos = resource.bind(async ({ filter }) => {
  "use cache";
  const res = await fetch(`/api/todos?filter=${filter}`);
  return res.json();
});

// ルート-リソースバインディングをクライアント参照としてエクスポート。
// これは"use client"モジュールなので、エクスポートはRSC
// シリアライゼーションを通過し、クライアント専用ナビゲーション時にクライアントで解決されます。
export const todosClientMapping = todos.from(({ search }) => ({
  filter: search.filter ?? "all",
}));

コンポーネントは.use()を直接呼び出します — データが利用可能になるまで(React.use()経由で)サスペンドします。サスペンション中にフォールバックを表示するには、ルートのloadingプロップを使用します。リソースが無効化されると、.use()を呼び出したコンポーネントは自動的に再レンダリングされ、新しいデータを再取得します。

"use client";
import { todos } from "./resources/todos/client";

export default function TodosPage() {
  const data = todos.use({ filter: "active" });
  return <ul>{data.items.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}

ルーターでは、サーバーとクライアントのバインディングをresources配列に並べて配置します。Route.jsxが自動的にパーティション分けします — サーバーバインディングはサーバーでロードされ、クライアント参照はクライアント専用ナビゲーション用にRSCを通過します:

import { todos } from "./todos/server";

export const todosServerMapping = todos.from(({ search }) => ({
  filter: search.filter ?? "all",
}));
import { todosServerMapping } from "./resources/mappings";
import { todosClientMapping } from "./resources/todos/client";

const router = createRouter({
  todos: createRoute(routes.todos, <TodosPage />, {
    loading: TodosLoading,
    resources: [todosServerMapping, todosClientMapping],
  }),
});

resourcesプロップは、サーバーサイドバインディング配列(サーバーでロード)と"use client"モジュールからのクライアント参照の両方を受け付けます。クライアント参照はサーバー上では不透明です — RSCシリアライゼーションを通過し、クライアントで解決されます。そこでRouteコンポーネントがクライアント専用ナビゲーションのプリローディングに自動的に登録します。

<Link name="use-hook"> ## コンポーネントでリソースを使う </Link>

.use(key) — Suspense統合フック

コンポーネントでリソースを消費する主要な方法です。ローダーを呼び出し、キーをバリデーションし、データが利用可能になるまで(React.use()経由で)サスペンドします。

リソースが無効化されると、そのリソースで.use()を呼び出したすべてのコンポーネントが自動的に再レンダリングされ、再取得します。新しいデータのロード中は、ローディングフォールバック(ルートのloadingプロップまたは<Suspense>バウンダリ)が表示されます。

import { user as userRoute } from "./routes";
import { userById, currentUser } from "./resources";

export default function UserPage() {
  const { id } = userRoute.useParams();
  const userData = userById.use({ id });
  const me = currentUser.use();

  return (
    <div>
      <h1>{userData.name}</h1>
      <p>{userData.email}</p>
      {me.id === userData.id && <EditButton userId={id} />}
    </div>
  );
}

リソースはルートに結合されていません — どこでも動作します:

function UserTooltip({ userId }) {
  const user = userById.use({ id: userId });
  return <span>{user.name}</span>;
}

.query(key) — 命令型、Promiseを返す

Reactのレンダーサイクル外で使用 — イベントハンドラ、サーバーアクション、スクリプト。

const user = await userById.query({ id: 42 });

.prefetch(key) — キャッシュウォーム、サスペンドなし

ファイア・アンド・フォーゲット。結果を待たずにキャッシュを投入するためにローダーをトリガーします。

userById.prefetch({ id: 42 });

エラーハンドリング

  • .use()はエラーをスローします — Reactのエラーバウンダリでキャッチ(Suspenseと一貫性あり)
  • .query()はリジェクトされたPromiseを返します — try/catchで処理
<Link name="invalidation"> ## 無効化 </Link>

無効化はキャッシュされたデータをクリアし、現在リソースを使用しているすべてのコンポーネントの再レンダリングをトリガーします。新しいデータのロード中、コンポーネントは再サスペンドします — ルートのloadingフォールバック(または最も近い<Suspense>バウンダリ)が自動的に表示されます。

内部的には、無効化は@lazarv/react-server/cacheの既存のinvalidate()関数に委譲します。リソースレイヤーはカスタムキャッシュロジックを追加しません — 内部のthenableキャッシュ(React.use()の参照安定性に使用)をクリアし、useSyncExternalStore経由でサブスクライブしているコンポーネントに通知します。

無効化はサーバーとクライアントの両方で動作します。クライアントでは、"use cache"が使用しているブラウザストレージプロバイダー(sessionStorage、localStorage、IndexedDB、インメモリ、またはカスタムUnstorageドライバー)からエントリをクリアします。

// リソースのすべてのキャッシュエントリを無効化
userById.invalidate();

// 特定のエントリを無効化
userById.invalidate({ id: 42 });

サーバーアクションからの使用

"use server";
import { userById, posts } from "./resources";

export async function updateUser(id, data) {
  await db.users.update(id, data);
  userById.invalidate({ id });
}

export async function deleteUser(id) {
  await db.users.delete(id);
  userById.invalidate({ id });
  posts.invalidate(); // postsも無効化
}

クライアントサイドの無効化

クライアントでは、無効化はブラウザキャッシュをクリアし、再レンダリングをトリガーします。.use()を呼び出したコンポーネントは再サスペンドし、ローディングフォールバックが表示され、新しいデータが取得されます。

"use client";
import { todos } from "./resources/todos/client";

function RefreshButton({ filter }) {
  return (
    <button onClick={() => todos.invalidate({ filter })}>
      更新
    </button>
  );
}
<Link name="create-resources"> ## リソースコレクション </Link>

createResourcesでリソースを型付きレジストリにグループ化します。すべてのリソースのキャッシュエントリを一度に破棄するinvalidateAll()を提供します。

import { createResources } from "@lazarv/react-server/resources";
import { userById } from "./user";
import { currentUser } from "./current-user";
import { posts } from "./posts";

export const resources = createResources({ userById, currentUser, posts });

// すべてを無効化
resources.invalidateAll();
<Link name="route-binding"> ## ルート-リソースバインディング </Link>

ルートはcreateRouteresourcesオプションを介してリソース依存関係を宣言します。ルートがマッチすると、すべてのバインドされたリソースが並列でロードされ、コンポーネントツリーのレンダリング前にデータを待ちます。これにより逐次ウォーターフォールが排除されます — コンポーネントが1つずつ.use()を呼び出す代わりに、すべてのローダーが同時に実行されます。

リソースはルートバインディングなしでも動作します。任意のコンポーネントが.use()を直接呼び出すことができ — データが利用可能になるまでサスペンドします。ルート-リソースバインディングは、ロードをルートレベルに移動する最適化です。

import { createRoute, createRouter } from "@lazarv/react-server/router";
import * as routes from "./routes";
import { userById } from "./resources/user";
import { currentUser } from "./resources/current-user";
import { posts } from "./resources/posts";

const router = createRouter({
  user: createRoute(routes.user, <UserPage />, {
    resources: [
      // .from()は { params, search } → リソースキーにマッピング
      userById.from(({ params }) => ({ id: params.id })),
      // シングルトンリソースは.from()不要
      currentUser,
    ],
  }),

  posts: createRoute(routes.posts, <PostListPage />, {
    resources: [
      // 現在のページと次のページを並列でロード
      posts.from(({ search }) => ({
        page: search.page ?? 1,
        tag: search.tag,
      })),
      posts.from(({ search }) => ({
        page: (search.page ?? 1) + 1,
      })),
    ],
  }),
});

export default router;

.from(mapFn)

{ resource, mapFn }バインディングタプルを返します。各.from()は個別のバインディングです — 同じリソースを異なるキーマッピングで複数回バインドできます(例:現在のページと次のページを並列でロード)。

デュアルローダーリソースの場合、クライアントバインディング("use client"モジュールから)をサーバーバインディングとresources配列に並べて配置します。Route.jsxが自動的にパーティション分けします — 特別な配線は不要です。

ナビゲーションフロー

サーバールート:

  1. ナビゲーション開始(Linkクリック、useNavigate()、戻る/進む)
  2. サーバーがルートをレンダリング、resources: [...]バインディングを発見
  3. すべてのリソースローダーが並列で実行 — ルートはすべてを待機
  4. コンポーネントがレンダリング、.use()を呼び出し — データは既にロード済み、サスペンションなし

クライアント専用ルート(クライアントリソースバインディング付き):

  1. ナビゲーション開始
  2. ルート登録済みローダーが並列で発火(ファイア・アンド・フォーゲット)
  3. URLが更新、コンポーネントがレンダリング、.use()を呼び出し
  4. データが準備完了なら — 即座にレンダリング。まだロード中なら — ローディングフォールバックでサスペンド

クライアント専用ナビゲーション

リソースバインディングには、サーバーからクライアントに直接シリアライズできないmapFn関数が含まれます。解決策:"use client"モジュールでバインディングを定義してエクスポートします。"use client"のエクスポートはクライアント参照なので、RSCシリアライゼーションを通過してクライアントで解決されます。

サーバーバインディングとクライアント参照をresources配列に並べて配置します。Route.jsxが$$typeofにより自動的にパーティション分けします — サーバーバインディングはサーバーでロード、クライアント参照はRSCを通過:

"use client";
import { todos as resource } from "./resource";

export const todos = resource.bind(async ({ filter }) => {
  "use cache";
  const res = await fetch(`/api/todos?filter=${filter}`);
  return res.json();
});

// クライアント参照としてエクスポート — RSCを通過してクライアントへ
export const todosClientMapping = todos.from(({ search }) => ({
  filter: search.filter ?? "all",
}));
import { todosServerMapping } from "./resources/mappings";
import { todosClientMapping } from "./resources/todos/client";

createRoute(routes.todos, <TodosPage />, {
  // サーバーバインディングは初回リクエストでロード、クライアント参照は
  // クライアント専用ナビゲーションのプリローディング用にRSCを通過。
  resources: [todosServerMapping, todosClientMapping],
});

Routeコンポーネントはクライアント参照を検出してクライアントに渡し、クライアント専用ナビゲーション中のプリローディングに自動的に登録されます。

高度なケース(動的登録、Routeに紐付かないリソース)の場合、@lazarv/react-server/navigationregisterRouteResourcesもプログラマティックAPIとして利用できます。

プリフェッチ

プリフェッチはルートロードとは別です。リソースディスクリプタの.prefetch()メソッドを使用して、事前にキャッシュをウォームします — 例えば、組み込みのprefetchプロップを使用したLinkホバー時や、予期されるナビゲーション前のプログラム的な呼び出し。

// プログラム的プリフェッチ — ファイア・アンド・フォーゲット
userById.prefetch({ id: 42 });
<Link name="loading"> ## ローディング状態 </Link>

ルートのloadingプロップは、サーバーとクライアントコンポーネントルートの両方にローディングフォールバックを提供します。コンポーネントが.use()を呼び出してデータがまだ準備できていない場合、<Suspense>経由でローディングコンポーネントが自動的に表示されます。

const router = createRouter({
  todos: createRoute(routes.todos, <TodosPage />, {
    loading: TodosLoading,
    resources: [...],
  }),
});
"use client";
export default function TodosLoading() {
  return <div>Loading todos...</div>;
}

ローディングフォールバックが表示されるタイミング:

  • リソースローダーがまだ完了していない初回レンダリング時
  • 無効化後、新しいデータの取得中
  • クライアント専用ナビゲーション中、ルートリソースのロード中
<Link name="identity"> ## 参照によるアイデンティティ </Link>

リソースは文字列名ではなくオブジェクト参照で識別されます。名前レジストリも文字列キーの衝突もありません。これがデュアルローダーリソースを機能させるものです:サーバーとクライアントの両方のローダーが同じディスクリプタオブジェクトで.bind()を呼び出すので、任意のコンポーネントの.use()が正しいローダーを見つけます。

// 共有ディスクリプタ — アイデンティティの単一ソース
export const todos = createResource({ key: { filter: String } });
import { todos as resource } from "./resource";
// .bind()がサーバーローダーを共有ディスクリプタにアタッチ
export const todos = resource.bind(async ({ filter }) => { /* ... */ });
"use client";
import { todos as resource } from "./resource";
// 同じディスクリプタ参照 — .bind()がクライアントローダーをアタッチ
export const todos = resource.bind(async ({ filter }) => { /* ... */ });

シンプルな(非デュアルローダー)リソースの場合、アイデンティティは暗黙的です — ディスクリプタとローダーはcreateResource({ ... }).bind(loaderFn)で同じファイルに定義されます。

<Link name="file-system-resources"> ## ファイルシステムベースのリソース </Link>

ファイルシステムベースのルーターは、宣言的でルートバインドされたデータ取得をサポートします。createResource.bind().from()を手動で配線する代わりに、いくつかの名前付き値をエクスポートするだけで、ルーターがすべてのボイラープレートを自動生成します。リソースファイルを作成するには、ファイル名に.resourceセグメントを追加します — 例えば、todos.resource.ts

リソースファイルから作成されたリソースディスクリプタは、仮想モジュール@lazarv/react-server/resources経由で公開されます。resourcesオブジェクトは名前付きディスクリプタの型付きコレクションです — 名前はファイル名から導出されます(例:todos.resource.tsresources.todos)。

規約

リソースファイルは以下の名前付きエクスポートをエクスポートします:

エクスポート 必須 説明
key いいえ リソースキースキーマ(例:{ filter: String }、Zodスキーマなど)。シングルトンリソースでは省略。
loader はい バリデーション済みのキーを受け取ってデータを取得する非同期関数。
mapping はい ルートパラメータと検索パラメータをリソースキーにマッピングする関数({ params, search }) => key。型安全なparamsとsearchにはroute.createResourceMapping(fn)を使用。
name いいえ 自動導出されたリソース名をオーバーライド。

ファイル名は、他のファイルシステムベースのルーターファイルと同様にルートパスを決定します。例えば、todos.resource.tsはリソースを/todosルートにバインドします。

import { todos } from "@lazarv/react-server/routes";

export const key = { filter: String };

export const loader = async ({ filter }) => {
  "use cache";
  return db.todos.list({ filter });
};

export const mapping = todos.createResourceMapping(({ search }) => ({
  filter: search.filter ?? "all",
}));

ページコンポーネントはresourcesコレクション経由でリソースにアクセスします:

"use client";

import { resources } from "@lazarv/react-server/resources";

export default function TodosPage() {
  const data = resources.todos.use({ filter: "all" });
  return <ul>{data.items.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}

仕組み

ルーターがすべての配線を行います。リソースファイルを見つけると:

  1. ディスクリプタを作成 — エクスポートされたkeyを使用してcreateResource({ key })
  2. ローダーをバインド.bind(loader)でエクスポートされたローダー関数をアタッチ
  3. ルートバインディングを作成.from(mapping)でルートパラメータ/検索をリソースキーにマッピング
  4. バインディングを登録 — ナビゲーション時に並列ロードされるようにルートのresources配列にリソースを追加
  5. コレクションに追加 — 仮想モジュール@lazarv/react-server/resourcesresources.<name>としてディスクリプタを公開

これにより、リソースファイルでcreateResource.bind().from()を自分で呼び出す必要はありません — パーツをエクスポートするだけです。

内部的には、ルーターは手動で書くのとまったく同じものを生成します:

手動(型付きルーター) リソースファイルの等価物
createResource({ key }) export const key = ...
.bind(loaderFn) export const loader = ...
ルート設定の.from(mapFn) export const mapping = ...
createResources({ ... }) 自動生成されるresourcesコレクション

リソースの命名

リソース名 — resourcesコレクションのキーとして使用 — はファイル名から導出されます:

ファイル名 導出される名前
todos.resource.ts todos
user-profile.resource.ts userProfile
(server).todos.resource.ts todos
(client).todos.resource.ts todos
posts/index.resource.ts posts

(server)(client)のような括弧付きプレフィックスは除去されます。ハイフンはキャメルケースに変換されます。

nameをエクスポートして導出名をオーバーライドします:

export const name = "todoList";
// → resources.todoList

クライアントリソースファイル

リソースファイルの先頭に"use client"を追加して、クライアントサイドローダーにします。クライアントリソースは完全にブラウザで実行されます — サーバーラウンドトリップなし。データはサポートされるストレージプロバイダー(sessionStorage、localStorage、IndexedDB、インメモリ、またはカスタムUnstorageドライバー)を使用して"use cache"でクライアントサイドにキャッシュされます。

"use client";

import { search as searchRoute } from "@lazarv/react-server/routes";

export const key = { q: String };

export const loader = async ({ q }) => {
  "use cache";
  const res = await fetch(`/api/search?q=${q}`);
  return res.json();
};

export const mapping = searchRoute.createResourceMapping(({ search }) => ({
  q: search.q ?? "",
}));

デュアルローダーリソースファイル

括弧付きプレフィックスを使用して、同じルートパスに複数のリソースをバインドします。プレフィックスはパスから除去されます — ファイルを区別するためのラベルとしてのみ機能します。

pages/
  todos.page.tsx
  todos.loading.tsx
  (server).todos.resource.ts   # サーバーローダー — 初回リクエストでロード
  (client).todos.resource.ts   # クライアントローダー — ナビゲーション時に引き継ぎ

両方のファイルが/todosにバインドされ、同じリソース名(todos)を共有します。初回のサーバーレンダリングでは、サーバーリソースがデータをロードしてクライアントにハイドレーションします。その後のクライアントサイドナビゲーションでは、クライアントリソースがブラウザで直接ローダーを実行します。ハイドレーション注入が正しく動作するように、両方のリソースファイルは同じキースキーマを使用する必要があります。

import { todos } from "@lazarv/react-server/routes";
import { loadTodos } from "../src/todos-loader";

export const key = { filter: String };

export const loader = async ({ filter }) => {
  "use cache: request";
  return loadTodos({ filter });
};

export const mapping = todos.createResourceMapping(({ search }) => ({
  filter: search.filter ?? "all",
}));
"use client";

import { todos } from "@lazarv/react-server/routes";
import { loadTodos } from "../src/todos-loader";

export const key = { filter: String };

export const loader = async ({ filter }) => {
  "use cache";
  return loadTodos({ filter });
};

export const mapping = todos.createResourceMapping(({ search }) => ({
  filter: search.filter ?? "all",
}));

ページコンポーネントはコレクションからresources.todosを使用します — 個々のリソースファイルからインポートする必要はありません:

"use client";

import { resources } from "@lazarv/react-server/resources";

export default function TodosPage() {
  const data = resources.todos.use({ filter: "all" });

  return (
    <div>
      <ul>{data.items.map(t => <li key={t.id}>{t.title}</li>)}</ul>
      <button onClick={() => resources.todos.invalidate({ filter: "all" })}>
        更新
      </button>
    </div>
  );
}

デュアルリソースが同じ名前を共有する場合、ルーターはresourcesコレクションにクライアントディスクリプタを優先します。これにより、クライアントコンポーネントのresources.todos.use()がナビゲーション時にクライアントローダーに解決され、サーバーローダーはルートバインディング経由の初回サーバーレンダリングに使用されます。

<Link name="api-reference"> ## APIリファレンス </Link>

createResource(options?)

リソースディスクリプタを作成します(ローダーなし)。

オプション 説明
key ValidateSchema | Record<string, Function> キースキーマまたはパースマップ。シングルトンでは省略。

.use().query().prefetch().invalidate().from()を持つResourceDescriptorを返します。

.bind(loaderFn)

ディスクリプタにローダーをバインドします。ディスクリプタを変更し、TData型がローダーの戻り値の型に絞り込まれた状態で返します。

const userById = createResource({
  key: z.object({ id: z.coerce.number() }),
}).bind(async ({ id }) => {
  "use cache";
  return db.users.findById(id);
});
パラメータ 説明
loaderFn (key) => T | Promise<T> データ取得関数。キャッシュには"use cache"を追加。

TDataがローダーの戻り値の型に絞り込まれた同じResourceDescriptorを返します。

createResources(resources)

リソースをレジストリにまとめます。

パラメータ 説明
resources Record<string, ResourceDescriptor> 名前付きリソース

リソースにinvalidateAll()を加えて返します。

ResourceDescriptorメソッド

メソッド 説明
.use(key?) Reactフック — データが利用可能になるまでサスペンド。無効化時に再レンダリング。
.query(key?) 命令型 — Promiseを返す
.prefetch(key?) ファイア・アンド・フォーゲットのキャッシュウォーミング
.invalidate(key?) キャッシュをクリアし、すべての.use()消費者の再レンダリングをトリガー
.from(mapFn) ルートバインディングを作成。デュアルローダールートではresources配列にクライアントバインディングと並べて配置。
.bind(loaderFn) ローダー関数をアタッチ。絞り込まれたTData型のディスクリプタを返す。

registerRouteResources(path, resources)

クライアント専用ナビゲーション用のリソースバインディングを登録するプログラマティックAPI。ほとんどの場合、createRouteresourcesプロップ経由でクライアントリソースバインディングを渡すことを推奨します — Routeコンポーネントが登録を自動的に処理します。

高度なケースで使用:動的登録、Routeコンポーネントに紐付かないリソース、命令的セットアップ。

パラメータ 説明
path string ルートパスパターン(例:"/todos"
resources (RouteResourceBinding | RouteResource)[] .from()またはベアディスクリプタから

クリーンアップ関数を返します。

import { registerRouteResources } from "@lazarv/react-server/navigation";
registerRouteResources("/todos", [
  todos.from(({ search }) => ({ filter: search.filter ?? "all" })),
]);