---
title: "React Routerのaction・loaderの使い方｜clientAction/clientLoaderの違いとv8対応"
url: "https://www.issoh.co.jp/tech/details/4436/"
published: 2024-12-09
updated: 2026-07-23
categories: ["React"]
publisher: "株式会社一創"
---

# React Routerのaction・loaderの使い方｜clientAction/clientLoaderの違いとv8対応

React Routerでデータを読むのが `loader`、書き込むのが `action` です。名前が似た `clientLoader` と `clientAction` はブラウザ側で動く別物で、どちらを書くかでSSRの挙動もバンドルサイズも変わります。この記事では4つのAPIの実行タイミングと使い分けを実コードで整理し、2026年6月17日にリリースされたv8で `context` と `url` 引数がどう変わったかまで踏み込みます。

## まとめ：actionとloaderの役割分担

- `loader`＝読み取り、`action`＝書き込み。`action` が完了すると、ページ上の全 `loader` データが自動で再検証される
- `loader` と `action` はサーバ専用でクライアントバンドルから除去される。DBクライアントやAPIキーをそのまま書いてよい
- `clientLoader` / `clientAction` はブラウザ専用。両方定義した場合、書き込みは `clientAction` が優先される
- 型は手書きせず、フレームワークモードのtypegenが出す `Route.LoaderArgs` / `Route.ComponentProps` を使う
- npmの最新は 8.3.0（2026年7月22日公開）。v8ではmiddlewareが既定になり、`context` は常に `RouterContextProvider` になった

以下、4つのAPIの境界線から順に見ていきます。

## loaderとclientLoaderの実行タイミングの違い

### サーバだけで動くloaderとRoute.LoaderArgs

公式ドキュメントは `loader` について「クライアントバンドルから削除されるため、ブラウザに含まれる心配なくサーバ専用APIを使える」と明記しています。初回表示ではサーバ（またはプリレンダリング時のビルド）で実行され、クライアント遷移のときはReact Router自身が自動で `fetch` を投げて呼び出します。つまり遷移のたびにネットワーク往復が1回発生する前提で設計します。

```
// app/routes/product.tsx
import type { Route } from "./+types/product";

export async function loader({ params }: Route.LoaderArgs) {
  // DB接続やAPIキーを直接書ける（ブラウザには出力されない）
  return db.getProduct(params.pid);
}

export default function Product({ loaderData }: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}
```

戻り値は `Response` でなくプレーンなオブジェクトで構いません。コンポーネントは `useLoaderData` を呼ばず、props の `loaderData` で受け取るのがフレームワークモードの標準形です。

### ブラウザで動くclientLoaderとHydrateFallback

`clientLoader` はブラウザだけで実行されます。SPAとして組む場合や、ログイン後にしか呼べない外部APIを叩く場合はこちらです。初回ロード時、ルートコンポーネントは `clientLoader` の完了後にしか描画されません。その間に何も出さないと画面が空になるため、`HydrateFallback` を同じファイルからexportします。

```
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
  const res = await fetch("/api/products/" + params.pid);
  return res.json();
}

export function HydrateFallback() {
  return <p>読み込み中です</p>;
}
```

`loader` と `clientLoader` を両方書いたうえで、初回ハイドレーション時にも `clientLoader` を走らせたいときは `clientLoader.hydrate = true as const;` を追加します。`as const` は型推論のために必要で、省くと `loaderData` の型が崩れます。

### 両方定義したときの既定動作とserverLoaderによる合成

両方をexportした場合の既定動作は「初回SSR（またはプリレンダリング）は `loader`、以降のクライアント遷移は `clientLoader`」です。`clientLoader` の引数には `serverLoader` が渡るので、サーバのデータを取り直しつつブラウザ固有の情報を足す、といった合成ができます。

```
export async function clientLoader({ params, serverLoader }: Route.ClientLoaderArgs) {
  const server = await serverLoader();
  const stock = await fetch("/api/stock/" + params.pid).then((r) => r.json());
  return { ...server, ...stock };
}
```

この合成は便利ですが、`serverLoader()` を呼んだ時点でサーバ往復が発生します。在庫数のように更新頻度が高い一部フィールドだけをブラウザで取り直す用途に限定し、ページ全体のデータ取得を `clientLoader` に移すのは避けてください。ルーティングそのものの書き方から確認したい場合は、[React Routerの基本ルーティングとmeta・title設定](/tech/details/7618/)を先に押さえると全体像がつかめます。

## actionとclientActionによるデータ更新と自動再検証

### Formからactionを呼ぶ最小構成

`action` はサーバ側でのデータ更新を担当します。`<Form method="post">` の送信先は既定で同じルートで、受け取りは通常のWeb標準どおり `request.formData()` です。結果はコンポーネントの `actionData` に入ります。

```
// app/routes/project.tsx
export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const title = formData.get("title");
  return db.updateProject({ title });
}

export default function Project({ actionData }: Route.ComponentProps) {
  return (
    <Form method="post">
      <input type="text" name="title" />
      <button type="submit">保存</button>
      {actionData ? <p>{actionData.title} を更新しました</p> : null}
    </Form>
  );
}
```

ここで効いてくるのが自動再検証です。公式ドキュメントは「actionが完了すると、ページ上の全loaderデータが再検証され、コードを一切書かなくてもUIがデータと同期する」と説明しています。更新後に一覧を手で再取得するコードは不要で、書いてしまうと二重リクエストになります。特定のルートだけ再検証を止めたいときは、そのルートに `shouldRevalidate` をexportします。

### clientActionが優先されるルールと使いどころ

`clientAction` はブラウザだけで実行され、**`action` と両方定義されている場合は `clientAction` が優先されます**。この優先順位を知らずに両方書くと、サーバ側の更新処理が呼ばれない事故になります。

```
export async function clientAction({ request }: Route.ClientActionArgs) {
  const formData = await request.formData();
  await someApi.updateProject({ title: formData.get("title") });
  return { ok: true };
}
```

使うべき場面ははっきりしています。localStorageやIndexedDBの更新、ブラウザにしか置いていないトークンで直接叩く外部API、そしてSPAモードでサーバ処理そのものが存在しない構成です。逆に、決済・在庫引き当て・権限変更といった改ざんされて困る処理を `clientAction` だけで完結させるのは設計ミスです。入力値の検証も更新もブラウザ側にあるため、DevToolsから任意のリクエストを送られれば素通りします。

### Form・useSubmit・useFetcherの使い分け基準

呼び出し方は3通りあり、判断軸は「画面遷移させたいか」の1点です。`<Form>` と `useSubmit` はナビゲーションを起こし、ブラウザの履歴エントリを追加します。`useFetcher` は**ナビゲーションも履歴追加も起こしません**。

一覧の各行にある「完了にする」ボタンや、いいねボタンのように画面に留まったまま更新するUIは `useFetcher` です。逆に、フォーム送信後に完了画面へ進む流れは `<Form>` を使います。入力から確認、送信までの多段フォームをどう組むかは、[確認画面付きフォームの実装とデータ保持](/tech/details/3793/)で扱っている設計がそのまま応用できます。

## loaderData・actionDataの型安全とtypegenの生成タイミング

フレームワークモードでは、ルートごとの型がtypegenで自動生成されます。`import type { Route } from "./+types/product";` の1行で、そのルート専用の `Route.LoaderArgs`・`Route.ActionArgs`・`Route.ComponentProps` が使えます。`params.pid` はルートパスの定義から型付けされるので、パラメータ名のtypoはコンパイル時に落ちます。

注意点は、生成物がビルド時に作られることです。ルート定義を変えた直後にエディタが型エラーを出す場合、多くは型生成が走っていないだけで、開発サーバの起動または `react-router typegen` の実行で解消します。`useLoaderData` を手動ジェネリクスで型付けする書き方はv7以降の推奨ではありません。propsの `loaderData` に寄せてください。

## fetcher.formDataで作る楽観的UI

楽観的UIは、サーバの応答を待たずに成功した状態を先に描く手法です。React Routerでは専用のライブラリを足す必要がなく、`fetcher.formData` に送信中の値が入っていることを利用します。

```
function Task({ task }) {
  const fetcher = useFetcher();
  let isComplete = task.status === "complete";
  if (fetcher.formData) {
    isComplete = fetcher.formData.get("status") === "complete";
  }
  return (
    <fetcher.Form method="post">
      <button name="status" value={isComplete ? "incomplete" : "complete"}>
        {isComplete ? "未完了に戻す" : "完了にする"}
      </button>
    </fetcher.Form>
  );
}
```

送信が失敗すれば `fetcher.formData` は消え、表示は元のデータに戻ります。ロールバック処理を自分で書く必要はありません。単に処理中であることを示したいだけなら `fetcher.state !== "idle"`、ページ遷移中のグローバルなスピナーなら `useNavigation` の `navigation.location` を見ます。

## loaderとactionが使えるモードの境界

React Routerには3つのモードがあり、データAPIが使えるかどうかはここで決まります。**Declarative Modeでは `loader` も `action` も使えません**。「react router data router」で検索して情報が食い違うのは、参照している記事のモードが違うためです。

| モード         | ルート定義               | loader / action | clientLoader / clientAction | SSR・プリレンダリング |
| ----------- | ------------------- | --------------- | --------------------------- | ------------ |
| Declarative | JSX（BrowserRouter）  | 不可              | 不可                          | 組み込みなし       |
| Data        | createBrowserRouter | 可               | 不可                          | 組み込みなし       |
| Framework   | routes.ts＋Viteプラグイン | 可               | 可                           | 組み込みあり       |

Data Modeはルート定義をReactのレンダリングの外へ出すことで `loader`・`action`・`useFetcher` を有効にする層で、Framework ModeはそのData ModeをViteプラグインで包み、型安全な `href`、ルートモジュールAPI、コード分割、SSRと静的プリレンダリングを足したものです。`clientLoader` と `clientAction` はルートモジュールのexportとして定義するAPIなので、Framework Modeの機能と考えて構いません。ルーターの選定段階で迷っているなら、[TanStack Routerの特徴と採用理由](/tech/details/9913/)との比較も判断材料になります。

## React Router v8でloader・actionまわりに入った変更

npmの `latest` は 8.3.0（2026年7月22日公開）で、8.0.0のリリースは2026年6月17日です。v7.0.0が2024年11月22日ですから、約1年7か月ぶりのメジャー更新にあたります。v8からは年次のメジャーリリースへ移行し、最低要件はNode 22.22以上、React 19.2.7以上、フレームワークモードはVite 7以上に引き上げられました。日本語の解説記事はv7時点で止まっているものが大半なので、ここは実際に手を動かす前に確認しておく価値があります。

### middlewareが既定化しcontextはRouterContextProviderに固定

v7で `future.v8_middleware` の裏にあったmiddlewareが、v8では常時有効になりました。これに伴い、`loader` / `action` / middleware が受け取る `context` は必ず `RouterContextProvider` のインスタンスになります。素のオブジェクトを渡していた構成は動きません。

```
import { createContext } from "react-router";

export const userContext = createContext<User | null>(null);

export async function loader({ context }: Route.LoaderArgs) {
  const user = context.get(userContext);   // 型安全に取り出す
  return { profile: await getProfile(user) };
}
```

カスタムサーバを立てている場合は、`getLoadContext` が `new RouterContextProvider()` を返し、`context.set()` で値を詰める形に書き換えます。`react-router-serve` をそのまま使っているなら変更は不要です。

### request.urlの正規化廃止と新しいurl引数

`future.v8_passThroughRequests` の既定化で、`request` は受信したものがそのまま渡るようになりました。従来 `request.url` から `.data` サフィックスやindex用の検索パラメータが取り除かれていた前提のコードは、v8では意図せずデータリクエストのURLを見ることになります。正規化済みのURLは新しい `url` 引数から取ります。

```
// v7まで
export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  if (url.pathname === "/path") { }
}

// v8
export async function loader({ request, url }: Route.LoaderArgs) {
  if (url.pathname === "/path") { }                                  // 正規化済み
  const isDataRequest = new URL(request.url).pathname.endsWith(".data");
}
```

あわせてデータリクエストのURL形式も変わり、末尾スラッシュの有無を区別するために `/a/b/c.data` は `/a/b/c/_.data` に、ルートの `/_root.data` は `/_.data` になりました。CDNやリバースプロキシで `.data` のパスをマッチさせている設定は書き換えが必要です。

### react-router-dom廃止とmetaのdata→loaderData

v6のimportパスを延命していた `react-router-dom` が削除されました。`Link` や `useLocation` は `react-router` から、`RouterProvider` は `react-router/dom` からimportします。`meta` と `useMatches()` の `data` フィールドも廃止され、`loaderData` に統一されました。

```
// v7まで
export function meta({ data }: Route.MetaArgs) {
  return [{ title: data.title }];
}

// v8
export function meta({ loaderData }: Route.MetaArgs) {
  return [{ title: loaderData.title }];
}
```

### v8へ今すぐ上げるべきでない場面

移行そのものは軽い部類です。公式も破壊的変更はいずれもv7の時点でfutureフラグを立てて先行対応できると案内しています。それでも、次の条件に当てはまるなら見送りが妥当です。

第一に、Node 22.22またはReact 19.2.7へ上げられない環境。これは選択の余地がない足切りです。第二に、CommonJSのままビルドしている構成。v8はESM専用として公開されているため、バンドラ設定ごとの作業になります。第三に、Cloudflare向けに `@react-router/dev/vite/cloudflare` のdev proxyを使っている場合。これは削除され、Cloudflare公式の `@cloudflare/vite-plugin` への差し替えが前提になります。いずれも該当しないなら、v7系の最新である7.18.1（2026年6月29日公開）でfutureフラグを順に有効化し、動作を確認してから8系へ上げるのが最短です。

## よくある質問

### loaderとclientLoaderはどちらを使うべきですか？

SSRまたは静的プリレンダリングで初回表示を速くしたい、DBやシークレットを直接触りたいなら `loader` です。`loader` はクライアントバンドルから除去されるため、APIキーが漏れる心配がありません。SPAモードで運用している、あるいはブラウザにしか無い認証情報で外部APIを叩くなら `clientLoader` を選び、初回描画の空白を避けるために `HydrateFallback` を必ずexportしてください。両方定義した場合は初回SSRが `loader`、以降のクライアント遷移が `clientLoader` になります。

### actionを呼んだあと、一覧を再取得するコードは必要ですか？

不要です。`<Form>`・`useFetcher`・`useSubmit` のいずれから呼んだ場合も、`action` の完了時にページ上の全 `loader` データが自動で再検証されます。手動で再取得を書くと同じデータを二重に取りに行くことになります。逆に、重いクエリを持つルートだけ再検証を抑止したいときは、そのルートに `shouldRevalidate` をexportして条件を返します。

### actionとclientActionを両方定義したらどちらが動きますか？

`clientAction` が優先されます。サーバ側の `action` は呼ばれません。両方書く構成は、ブラウザ側で先にバリデーションやキャッシュ更新を行い、そのうえでサーバ処理へ流したい場合に限られます。改ざんされて困る処理をクライアント側だけで完結させるのは避けてください。

### React Router v8への移行は大変ですか？

破壊的変更はいずれもv7の段階でfutureフラグを立てて事前に対応できる設計になっています。実作業は `react-router-dom` のimport差し替え、`meta` の `data` を `loaderData` へ改名、カスタムサーバの `getLoadContext` が `RouterContextProvider` を返すことの確認、この3点が中心です。むしろ重いのはNode 22.22以上・React 19.2.7以上・Vite 7以上という実行環境側の要件で、ここが満たせるかを先に確認してください。

### Declarative Modeでもloaderは使えますか？

使えません。`loader` と `action` はData Mode以上で有効になります。`BrowserRouter` と `Routes` / `Route` をJSXで書くDeclarative Modeは、URLとコンポーネントの対応、`Link`、`useNavigate`、`useLocation` といった基本機能に限定されます。データAPIを使いたい場合は `createBrowserRouter` によるData Mode、または `routes.ts` とViteプラグインを使うFramework Modeへ移行してください。

## 関連記事

- [React Routerのmeta・title設定と基本ルーティング｜v8対応入門](/tech/details/7618/)
- [Reactで確認画面付きフォームを作る方法（入力→確認→送信とデータ保持）](/tech/details/3793/)
- [Material-UIとReact Routerを統合した画面遷移の実現方法](/tech/details/4299/)
- [TanStack Routerとは何か？概要・特徴・メリットとReact/SPA開発で注目される理由を解説](/tech/details/9913/)

---

出典: [React Routerのaction・loaderの使い方｜clientAction/clientLoaderの違いとv8対応](<https://www.issoh.co.jp/tech/details/4436/>)（株式会社一創）
