web-routing-react-router について
web-routing-react-routerは、Reactアプリを開発する人向けのReact Router v7 Data Modeガイドです。loader、action、fetcher、ナビゲーション状態、エラー処理を中心に、バージョン固有の注意点も解説します。
主な機能
- 01Data Modeのルーター設定パターン
- 02loaderとactionによるデータ処理
- 03fetcherを使った更新処理
- 04ルートエラーと保留状態
- 05ネストレイアウトとルート分割
活用シーン
- 01コンポーネント描画前のルートデータ読み込み
- 02actionによるフォームと更新処理
- 03URLを変えないデータ更新
- 04ルート単位のエラーと読み込み表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-routing-react-routerインストール後、Claude Code で/web-routing-react-routerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mddata-loading.mderror-handling.mdlayouts.mdnavigation.md詳細
React Router のパターン
クイックガイド: このスキルでは、React Router v7 の Data Mode、つまりフルフレームワークを使わない
createBrowserRouterとローダー、アクション、フェッチャー、保留中の状態を扱います。v7 では、回答を左右する変更が4つあります。すべてのエクスポートは"react-router"から取得し、RouterProviderを含む DOM 専用のものは"react-router/dom"からも取得できます。一方、react-router-domは非推奨の再エクスポートとして v7 でも残りますが、v8 で削除されます。json()とdefer()は削除されたため、ローダーはプレーンオブジェクトを返します。フォームのメソッド値は大文字("POST")です。また、アクションでエラーが発生した後、shouldRevalidateで再有効化を選択しない限り、ローダーは再検証をスキップします。
詳細なリソース:
- examples/core.md — ルーターのセットアップ、ルートレイアウト、ルートエラーバウンダリー、v7 移行時の2つの落とし穴
- examples/data-loading.md — ローダー、アクション、
<Form>、useFetcher、楽観的 UI、shouldRevalidate、遅延データ - examples/navigation.md — ローダーと連携する
Link、NavLink、useNavigate、redirect、useSearchParams - examples/error-handling.md — エラーのバブリング、スローされたレスポンス、保留中の UI、
route.lazy - examples/layouts.md —
Outlet、useOutletContext、パスのない認証レイアウト、useBlocker - reference.md — ルートのオプション、フックとコンポーネントの一覧、
createBrowserRouterのオプション
どのモードを使うか
React Router には3つのモードがあり、データ API を備えているのはそのうち1つだけです。
- Data Mode —
createBrowserRouter+<RouterProvider>。ローダー、アクション、フェッチャー、errorElementがすべて機能します。このスキルで扱うモードです。examples/core.md から始めてください。 - Declarative Mode —
<BrowserRouter>+<Routes>。URL のマッチングと<Link>のみです。ここではloaderプロパティは何も通知されずに無視されるため、読み込むデータがなく、このスキルのパターンがどれも当てはまらない場合に使ってください。 - Framework Mode — ルーター独自のバンドラープラグインを通じた、ファイルベースのルート、SSR、ストリーミング。独自の規約を持つ別の領域であり、ここでは扱いません。
<critical_requirements>
React Router のコードを書く前に
すべて "react-router" からインポートし、RouterProvider は "react-router/dom" からインポートします — このコピーは react-dom の flushSync を組み込みます。react-router-dom は v7 でも再エクスポートとしてインストールされるため、古いインポートがあってもビルドは失敗せず、何も通知されません。v8 ではこのパッケージが削除されます。
loader、action、fetcher を使う場合は、必ず createBrowserRouter と <RouterProvider> を使います。 これらは Data Mode の機能です。<BrowserRouter> ではプロパティは受け付けられますが無視され、エラーも通知されません。
loader からはプレーンなオブジェクト、または自分で作成した Response を返します。 json() と defer() は v7 で削除されました。
return redirect() ではなく throw redirect() を使います。 throw するとコールスタック全体が巻き戻されます。そのため、共有の requireAuth() ヘルパーが呼び出し元の loader を停止できます。
ルートには errorElement または ErrorBoundary を設定します。 これがツリー内で最後にエラーを捕捉する場所です。設定しない場合、loader の失敗ひとつでアプリがルーター組み込みのエラー情報に置き換わります。
</critical_requirements>
自動検出: createBrowserRouter、RouterProvider、useLoaderData、useActionData、useNavigation、useSearchParams、useFetcher、useRouteError、isRouteErrorResponse、useOutletContext、useRevalidator、useBlocker、Outlet、NavLink、errorElement、shouldRevalidate、route.lazy、HydrateFallback
対象:
- データ読み込み、フォームアクション、保留状態を含むルートツリー
- 共有 UI を保持するネストされたレイアウト
- ルート単位のエラーバウンダリーと Not Found の処理
- アプリケーションの状態としての URL 検索パラメーター
- 画面遷移を伴わない変更 — インラインフォーム、切り替え、自動保存
- ルート単位のコード分割
別途扱う内容:
- ルート間のサーバーデータのキャッシュと重複排除 — loader は画面遷移ごとに取得し、その間で何をキャッシュするかはデータ取得を担う側が決定します。
- URL に含めるべきでないクライアント状態 — このスキルで扱うのは URL に関係する状態のみです。
- アクティブなリンクや保留中のリンクのスタイル設定 —
NavLinkはその状態をclassNameまたはstyle関数に渡しますが、それらが返す値はこのスキルの対象外です。 - サーバーレンダリング、HTML ストリーミング、ファイルベースのルート生成。
React Router v7 は、ルーターを URL マッチャーではなくデータレイヤーとして扱います。ルートでは、コンポーネントがレンダリングされる前に、読み込むデータ(loader)、受け付ける変更(action)、失敗を捕捉する場所(errorElement)を宣言します。これによりオーケストレーションがコンポーネントの外に移り、マウント後にデータを取得するウォーターフォールがなくなります。
- ルートがデータを所有します。 コンポーネントはデータを受け取りますが、自身では取得しません。
- URL が信頼できる唯一の情報源です。 パスパラメーター、検索パラメーター、ナビゲーション状態はすべて URL に保持されます。
- エラーは上位に伝播します。 エラーは最も近い
errorElementに伝播し、親レイアウトは画面に残ります。 - 再検証は自動です。 action が成功すると、アクティブなすべての loader が再実行されるため、手動で無効化するキャッシュはありません。
<decision_framework>
ロジックを置く場所
What does this code do?
+-- Fetch data the component needs? -> loader (parallel with sibling loaders)
+-- Handle a form submission? -> action (reads FormData, returns errors or redirects)
+-- Mutate without changing the URL? -> useFetcher (independent state, runs concurrently)
+-- Gate access to a subtree? -> pathless layout route whose loader throws redirect()
ナビゲーションの方法
Where are you?
+-- In JSX, on something clickable? -> <Link> or <NavLink>
+-- In a handler, after a side effect? -> useNavigate()
+-- In a loader or action? -> throw redirect()
+-- Outside the React tree entirely? -> router.navigate() on the router object
最後の方法は、レンダリングされないモジュールから抜け出す唯一の手段です。たとえば、401 に応じてリダイレクトする fetch ラッパーが該当します。フックを使うには、いずれもコンポーネントが必要です。
エラーを捕捉する場所
確実にエラーを捕捉できるよう、ルートに errorElement を設定します。また、失敗ごとに個別の UI が必要なルートにも設定します。投稿が見つからない場合と、ダッシュボードの読み込みに失敗した場合では、表示すべき内容が異なります。loader から Response(throw new Response("Not Found", { status: 404 }))をスローすると、失敗が HTTP エラーとして扱われ、isRouteErrorResponse で型を絞り込めます。
</decision_framework>
基本パターン
パターン 1:Data Mode のセットアップ
ルートはオブジェクトとして定義し、React の外に配置します。これにより、ルーターは何かがマウントされる前に loader を実行できます。
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
errorElement: <RootError />,
children: [
{ index: true, element: <HomePage /> },
{ path: "posts", element: <PostsPage />, loader: postsLoader },
],
},
]);
コード全体:examples/core.md
### パターン 2:ローダーとアクション
ローダーはレンダリング前に実行され、アクションは送信を処理します。どちらも { request, params } を受け取り、request は標準の Web Request です。同階層のローダーは並列で実行され、親が子より先に実行されます。
export async function postsLoader({ request }: LoaderFunctionArgs) {
const response = await fetch("/api/posts");
if (!response.ok) throw new Response("Failed to load", { status: 500 });
return { posts: await response.json() };
}
export async function createPostAction({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const post = await createPost({ title: formData.get("title") });
throw redirect(`/posts/${post.id}`);
}
完全なコード:examples/data-loading.md
パターン 3:errorElement によるエラーバウンダリー
errorElement は、ルートのローダー、アクション、コンポーネントのいずれから発生したエラーも捕捉します。isRouteErrorResponse を使うと、スローされた Response と予期しない例外を区別できます。
function RouteError() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <p role="alert">{error.status}: {error.statusText}</p>;
}
return <p role="alert">{error instanceof Error ? error.message : "Unknown error"}</p>;
}
完全なコード:examples/error-handling.md
パターン 4:Outlet によるネストされたレイアウト
親ルートは、一致した子ルートが表示される場所に <Outlet /> をレンダリングします。子ルートへの移動後もレイアウトは維持されるため、サイドバーとその状態が保持されます。
function DashboardLayout() {
return (
<div>
<nav><NavLink to="/dashboard" end>Overview</NavLink></nav>
<main><Outlet /></main>
</div>
);
}
完全なコード:examples/layouts.md
パターン 5:useNavigation によるナビゲーション状態
navigation.state は "idle"、"loading"(ローダー実行中)、または "submitting"(アクション実行中)です。ルートレイアウトにインジケーターを 1 つ置けば、アプリ全体をカバーできます。
function GlobalSpinner() {
const navigation = useNavigation();
if (navigation.state === "idle") return null;
return <div role="progressbar" aria-busy="true" />;
}
完全なコード:examples/error-handling.md
パターン 6:useFetcher によるナビゲーションを伴わない変更
fetcher.Form は URL を変更せずにアクションへ送信します。各フェッチャーは独立した state と data を持ち、複数のフェッチャーを同時に実行できます。また、それぞれの処理が完了すると、実行中のローダーは再検証されます。
function DeleteButton({ postId }: { postId: string }) {
const fetcher = useFetcher();
return (
<fetcher.Form method="POST" action={`/posts/${postId}/delete`}>
<button type="submit" disabled={fetcher.state !== "idle"}>Delete</button>
</fetcher.Form>
);
}
完全なコード:examples/data-loading.md
パターン 7:route.lazy によるコード分割
route.lazy は、ナビゲーションが発生するまでルートモジュールの読み込みを遅らせます。関数形式では 1 つのチャンクが配信されます。v7.5+ のオブジェクト形式では各プロパティが分割されるため、ローダーとコンポーネントを並列にダウンロードできます。
{ path: "admin", lazy: () => import("./pages/admin") }
{
path: "admin",
lazy: {
loader: async () => (await import("./pages/admin.loader")).loader,
Component: async () => (await import("./pages/admin.component")).AdminPage,
},
}
完全なコード:examples/error-handling.md
<red_flags>
要注意事項
実行時に問題が発生します:
"react-router-dom"からインポートすると、v7 では非推奨で v8 では削除されます。現在も解決されてしまうため、問題があっても通知されません。"react-router"からインポートするか、RouterProviderには"react-router/dom"を使用してください。json()またはdefer()を呼び出すと、v7 では削除されているため問題が発生します。通常のオブジェクト、または遅延部分を表す Promise を返し、<Await>で読み取ってください。<BrowserRouter>配下の<Route>にloaderまたはactionを指定しても、警告なしに受け付けられたうえで無視されます。ルートツリーをcreateBrowserRouterに切り替えてください。- ツリー内に
errorElementがないと、ローダーのエラーによってアプリがルーターのデフォルトのエラー表示に置き換わります。ルートに設定してください。 - 共通ヘルパー内で
return redirect()を使うと、呼び出し元は戻り値としてResponseを受け取って処理を続行します。代わりにthrowしてください。 <Outlet />をレンダリングしないレイアウトでは、一致した子ルートが表示されず、何も通知されません。- ローダーのないルートのコンポーネントで
useLoaderData()を使うと、原因から離れた場所でundefinedが返されます。 navigation.formMethod === "post"は使用しないでください。v7 ではフォームメソッドが大文字に変換されるため、"POST"と比較してください。
意外な動作:
- アクションが成功すると、実行されたルートだけでなく、アクティブなローダーがすべて再検証されます。アクションが エラー になった場合は、
shouldRevalidateで再検証を有効にしない限り、どのローダーも再検証されません。 useFetcherはuseNavigationの状態を変化させません。この 2 つは独立しています。useSearchParamsのセッターはsetStateのようにキューイングされません。同じティック内で 2 回呼び出しても互いの結果は反映されないため、コールバック形式を使用してください。route.lazyではpath、index、children、idを指定できません。これらは静的設定に残してください。paramsの値は常に文字列です。コンポーネントではなくローダー内で解析してください。- リダイレクトせずにアクションからデータを返すと、その結果にアクセスできないため、更新すると変更前の UI が表示されます。
navigate(-1)は履歴エントリーがあることを前提としていますが、ディープリンクからアクセスしたユーザーにはそのエントリーがありません。
</red_flags>