ルートモジュール

はじめに

routes.ts で参照されるファイルは、ルートモジュールと呼ばれます。

app/routes.ts
route("teams/:teamId", "./team.tsx"),
//           ルートモジュール ^^^^^^^^

ルートモジュールは、React Router のフレームワーク機能の基礎であり、以下を定義します。

  • 自動コード分割
  • データローディング
  • アクション
  • 再検証
  • エラー境界
  • その他

このガイドは、すべてのルートモジュールの機能の簡単な概要です。残りの入門ガイドでは、これらの機能についてより詳しく説明します。

コンポーネント (default)

ルートモジュールの default エクスポートは、ルートが一致したときにレンダリングされるコンポーネントを定義します。

app/routes/my-route.tsx
export default function MyRouteComponent() {
  return (
    <div>
      <h1>見て!</h1>
      <p>
        10 年経っても React Router を使ってるよ。
      </p>
    </div>
  );
}

コンポーネントに渡される Props

コンポーネントがレンダリングされるとき、React Router が自動的に生成する Route.ComponentProps で定義された props が提供されます。これらの props には以下が含まれます。

  1. loaderData: このルートモジュールの loader 関数から返されるデータ
  2. actionData: このルートモジュールの action 関数から返されるデータ
  3. params: ルートパラメータを含むオブジェクト(存在する場合)。
  4. matches: 現在のルートツリー内のすべての一致の配列。

useLoaderDatauseParams のようなフックの代わりにこれらの props を使用できます。これらはルートに対して自動的に型付けされるため、こちらの方が望ましい場合があります。

Props の使用

app/routes/my-route-with-default-params.tsx
import type { Route } from "./+types/route-name";
 
export default function MyRouteComponent({
  loaderData,
  actionData,
  params,
  matches,
}: Route.ComponentProps) {
  return (
    <div>
      <h1>Props 付きのマイルートへようこそ!</h1>
      <p>ローダーデータ: {JSON.stringify(loaderData)}</p>
      <p>アクションデータ: {JSON.stringify(actionData)}</p>
      <p>ルートパラメータ: {JSON.stringify(params)}</p>
      <p>一致したルート: {JSON.stringify(matches)}</p>
    </div>
  );
}

middleware

ルートの middleware は、ドキュメントおよびデータリクエストの前後でサーバー上で順次実行されます。これにより、ロギング、認証、レスポンスの後処理などを行うための単一の場所が提供されます。next 関数はチェーンを下に続け、リーフルートでは next 関数がナビゲーションのローダー/アクションを実行します。

以下は、サーバー上のリクエストをログに記録するミドルウェアの例です。

root.tsx
async function loggingMiddleware(
  { request, context },
  next,
) {
  console.log(
    `${new Date().toISOString()} ${request.method} ${request.url}`,
  );
  const start = performance.now();
  const response = await next();
  const duration = performance.now() - start;
  console.log(
    `${new Date().toISOString()} Response ${response.status} (${duration}ms)`,
  );
  return response;
}
 
export const middleware = [loggingMiddleware];

以下は、ログインしているユーザーをチェックし、context にユーザーを設定して、ローダーからアクセスできるようにするミドルウェアの例です。

routes/_auth.tsx
async function authMiddleware({ request, context }) {
  const session = await getSession(request);
  const userId = session.get("userId");
 
  if (!userId) {
    throw redirect("/login");
  }
 
  const user = await getUserById(userId);
  context.set(userContext, user);
}
 
export const middleware = [authMiddleware];
ミドルウェアをルートに追加する際に、アプリケーションが意図したとおりに動作することを確認するために、[ミドルウェアがいつ実行されるか][when-middleware-runs]を理解していることを確認してください。

参照:

clientMiddleware

これは middleware のクライアントサイド版であり、クライアントナビゲーション中にブラウザで実行されます。サーバーミドルウェアとの唯一の違いは、クライアントミドルウェアがサーバー上で HTTP リクエストをラップしないため、Response を返さないことです。

以下は、クライアント上のリクエストをログに記録するミドルウェアの例です。

root.tsx
async function loggingMiddleware(
  { request, context },
  next,
) {
  console.log(
    `${new Date().toISOString()} ${request.method} ${request.url}`,
  );
  const start = performance.now();
  await next(); // 👈 Response は返されない
  const duration = performance.now() - start;
  console.log(
    `${new Date().toISOString()} (${duration}ms)`,
  );
  // ✅ 何も返す必要はありません
}
 
export const clientMiddleware = [loggingMiddleware];

参照:

loader

ルートローダーは、ルートコンポーネントがレンダリングされる前に、ルートコンポーネントにデータを提供します。サーバーレンダリング時、またはプリレンダリングによるビルド時にのみサーバー上で呼び出されます。

export async function loader() {
  return { message: "Hello, world!" };
}
 
export default function MyRoute({ loaderData }) {
  return <h1>{loaderData.message}</h1>;
}

参照:

clientLoader

ブラウザでのみ呼び出されるルートクライアントローダーは、ルートローダーに加えて、またはルートローダーの代わりに、ルートコンポーネントにデータを提供します。

export async function clientLoader({ serverLoader }) {
  // サーバーローダーを呼び出す
  const serverData = await serverLoader();
  // および/またはクライアントでデータをフェッチする
  const data = getDataFromClient();
  // useLoaderData() を通して公開するデータを返す
  return data;
}

クライアントローダーは、関数の hydrate プロパティを設定することで、サーバーレンダリングされたページの初期ページロードハイドレーションに参加できます。

export async function clientLoader() {
  // ...
}
clientLoader.hydrate = true as const;

as const を使用すると、TypeScript は clientLoader.hydrate の型を boolean ではなく true と推論します。 これにより、React Router は clientLoader.hydrate の値に基づいて loaderData の型を導出できます。

参照:

action

ルートアクションを使用すると、<Form>useFetcher、および useSubmit から呼び出されたときに、ページ上のすべてのローダーデータの自動再検証によるサーバー側のデータ変更が可能になります。

// route("/list", "./list.tsx")
import { Form } from "react-router";
import { TodoList } from "~/components/TodoList";
 
// このデータはアクションが完了した後にロードされます...
export async function loader() {
  const items = await fakeDb.getItems();
  return { items };
}
 
// ...そのため、ここのリストは自動的に更新されます
export default function Items({ loaderData }) {
  return (
    <div>
      <List items={loaderData.items} />
      <Form method="post" navigate={false} action="/list">
        <input type="text" name="title" />
        <button type="submit">Todo を作成</button>
      </Form>
    </div>
  );
}
 
export async function action({ request }) {
  const data = await request.formData();
  const todo = await fakeDb.addItem({
    title: data.get("title"),
  });
  return { ok: true };
}

参照:

clientAction

ルートアクションと同様ですが、ブラウザでのみ呼び出されます。

export async function clientAction({ serverAction }) {
  fakeInvalidateClientSideCache();
  // 必要に応じてサーバーアクションを呼び出すこともできます
  const data = await serverAction();
  return data;
}

参照:

ErrorBoundary

他のルートモジュール API が例外をスローすると、ルートコンポーネントの代わりにルートモジュールの ErrorBoundary がレンダリングされます。

import {
  isRouteErrorResponse,
  useRouteError,
} from "react-router";
 
export function ErrorBoundary() {
  const error = useRouteError();
 
  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h1>
          {error.status} {error.statusText}
        </h1>
        <p>{error.data}</p>
      </div>
    );
  } else if (error instanceof Error) {
    return (
      <div>
        <h1>エラー</h1>
        <p>{error.message}</p>
        <p>スタックトレースは次のとおりです:</p>
        <pre>{error.stack}</pre>
      </div>
    );
  } else {
    return <h1>不明なエラー</h1>;
  }
}

参照:

HydrateFallback

初期ページロードでは、ルートコンポーネントはクライアントローダーが完了した後にのみレンダリングされます。エクスポートされている場合、HydrateFallback はルートコンポーネントの代わりにすぐにレンダリングできます。

routes/client-only-route.tsx
export async function clientLoader() {
  const data = await fakeLoadLocalGameData();
  return data;
}
 
export function HydrateFallback() {
  return <p>ゲームをロード中...</p>;
}
 
export default function Component({ loaderData }) {
  return <Game data={loaderData} />;
}

headers

ルートヘッダーは、サーバーレンダリング時にレスポンスとともに送信される HTTP ヘッダーを定義します。

export function headers() {
  return {
    "X-Stretchy-Pants": "its for fun",
    "Cache-Control": "max-age=300, s-maxage=3600",
  };
}

参照:

handle

ルートハンドルを使用すると、アプリは useMatches のルート一致に何かを追加して、抽象化(パンくずリストなど)を作成できます。

export const handle = {
  its: "all yours",
};

参照:

ルートリンクは、ドキュメントの <head> にレンダリングされる <link> 要素 を定義します。

export function links() {
  return [
    {
      rel: "icon",
      href: "/favicon.png",
      type: "image/png",
    },
    {
      rel: "stylesheet",
      href: "https://example.com/some/styles.css",
    },
    {
      rel: "preload",
      href: "/images/banner.jpg",
      as: "image",
    },
  ];
}

すべてのルートリンクが集約され、通常はアプリのルートでレンダリングされる <Links /> コンポーネントを介してレンダリングされます。

import { Links } from "react-router";
 
export default function Root() {
  return (
    <html>
      <head>
        <Links />
      </head>
 
      <body />
    </html>
  );
}

meta

ルートの meta は、<Meta /> コンポーネントでレンダリングされる メタタグ を定義します。これは通常、<head> 内に配置されます。

React 19 以降では、ルートモジュールの meta export を使用するよりも、組み込みの <meta> 要素を使用することが推奨されています。

以下は、<meta> および <title> 要素の使用方法の例です。

export default function MyRoute() {
  return (
    <div>
      <title>非常にクールなアプリ</title>
      <meta property="og:title" content="非常にクールなアプリ" />
      <meta
        name="description"
        content="このアプリは最高です"
      />
      {/* ルートの残りのコンテンツ... */}
    </div>
  );
}
app/product.tsx
export function meta() {
  return [
    { title: "非常にクールなアプリ" },
    {
      property: "og:title",
      content: "非常にクールなアプリ",
    },
    {
      name: "description",
      content: "このアプリは最高です",
    },
  ];
}
app/root.tsx
import { Meta } from "react-router";
 
export default function Root() {
  return (
    <html>
      <head>
        <Meta />
      </head>
 
      <body />
    </html>
  );
}

最後のマッチしたルートの meta が使用され、親ルートの meta を上書きできます。meta 記述子の配列全体がマージされるのではなく、置き換えられることに注意することが重要です。これにより、異なるレベルのページ間で独自の meta コンポジションロジックを柔軟に構築できます。

参照

shouldRevalidate

SSR を使用するフレームワークモードでは、すべてのナビゲーションとフォーム送信後にルートローダーが自動的に再検証されます(これは データモード とは異なります)。これにより、ミドルウェアとローダーはリクエストコンテキストを共有し、データモードとは異なる方法で最適化できます。

この関数を定義すると、ナビゲーションおよびフォーム送信に関するルートローダーの再検証をオプトアウトできます。

SPA モードを使用する場合、ナビゲーション時に呼び出すサーバーローダーがないため、shouldRevalidateデータモード と同じように動作します。

import type { ShouldRevalidateFunctionArgs } from "react-router";
 
export function shouldRevalidate(
  arg: ShouldRevalidateFunctionArgs,
) {
  return true;
}

ShouldRevalidateFunctionArgs リファレンスドキュメント ↗


次: レンダリング戦略