RouterProviderからのフレームワーク採用

<RouterProvider>を使用していない場合は、代わりにコンポーネントルートからのフレームワーク採用を参照してください。

React Router Viteプラグインは、React Routerにフレームワーク機能を追加します。このガイドは、アプリでプラグインを採用するのに役立ちます。問題が発生した場合は、TwitterまたはDiscordでお問い合わせください。

機能

Viteプラグインは以下を追加します。

  • ルートローダー、アクション、および自動データ再検証
  • 型安全なルートモジュール
  • 自動ルートコード分割
  • ナビゲーション間の自動スクロール復元
  • オプションの静的プリレンダリング
  • オプションのサーバーレンダリング

初期設定には最も多くの作業が必要ですが、完了したら、新しい機能を段階的に採用できます。

前提条件

Viteプラグインを使用するには、プロジェクトに以下が必要です。

  • Node.js 20+(Nodeをランタイムとして使用する場合)
  • Vite 5+

1. ルート定義をルートモジュールに移動する

React Router Viteプラグインは独自のRouterProviderをレンダリングするため、既存のRouterProviderをその中にレンダリングすることはできません。代わりに、すべてのルート定義をルートモジュールAPIに一致するようにフォーマットする必要があります。

この手順には最も時間がかかりますが、React Router Viteプラグインを採用するかどうかとは関係なく、これを行うことでいくつかの利点があります。

  • ルートモジュールは遅延読み込みされるため、アプリの初期バンドルサイズが小さくなります
  • ルート定義が統一されるため、アプリのアーキテクチャが簡素化されます
  • ルートモジュールへの移行は段階的であり、一度に1つのルートを移行できます

👉 ルート定義をルートモジュールに移動する

ルートモジュールAPIに従って、ルート定義の各部分を個別の名前付きエクスポートとしてエクスポートします。

src/routes/about.tsx
export async function clientLoader() {
  return {
    title: "About",
  };
}
 
export default function About() {
  let data = useLoaderData();
  return <div>{data.title}</div>;
}
 
// clientAction, ErrorBoundaryなど

👉 変換関数を作成する

ルートモジュール定義をデータルーターで期待される形式に変換するヘルパー関数を作成します。

src/main.tsx
function convert(m: any) {
  let {
    clientLoader,
    clientAction,
    default: Component,
    ...rest
  } = m;
  return {
    ...rest,
    loader: clientLoader,
    action: clientAction,
    Component,
  };
}

👉 ルートモジュールを遅延読み込みして変換する

ルートモジュールを直接インポートする代わりに、遅延読み込みしてデータルーターで期待される形式に変換します。

ルート定義はルートモジュールAPIに準拠するだけでなく、ルートのコード分割の利点も得られます。

src/main.tsx
let router = createBrowserRouter([
  // ... その他のルート
  {
    path: "about",
-   loader: aboutLoader,
-   Component: About,
+   lazy: () => import("./routes/about").then(convert),
  },
  // ... その他のルート
]);

アプリの各ルートについてこの手順を繰り返します。

2. Viteプラグインをインストールする

すべてのルート定義をルートモジュールに変換したら、React Router Viteプラグインを採用できます。

👉 React Router Viteプラグインをインストールする

npm install -D @react-router/dev

👉 ランタイムアダプターをインストールする

Nodeをランタイムとして使用していると仮定します。

npm install @react-router/node

👉 ReactプラグインをReact Routerと交換する

vite.config.ts
-import react from '@vitejs/plugin-react'
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
 
 
export default defineConfig({
  plugins: [
-    react()
+    reactRouter()
  ],
});

3. React Router設定を追加する

👉 react-router.config.tsファイルを作成する

プロジェクトのルートに追加します。この設定では、アプリディレクトリの場所や、現時点ではSSR(サーバーサイドレンダリング)を使用しないことなど、プロジェクトに関する情報をReact Routerに伝えることができます。

touch react-router.config.ts
react-router.config.ts
import type { Config } from "@react-router/dev/config";
 
export default {
  appDirectory: "src",
  ssr: false,
} satisfies Config;

4. ルートエントリポイントを追加する

一般的なViteアプリでは、index.htmlファイルがバンドルのエントリポイントです。React Router Viteプラグインは、エントリポイントをroot.tsxファイルに移動するため、静的HTMLではなくReactを使用してアプリのシェルをレンダリングし、必要に応じてサーバーレンダリングにアップグレードできます。

👉 既存のindex.htmlroot.tsxに移動する

たとえば、現在のindex.htmlが次のようになっている場合。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

そのマークアップをsrc/root.tsxに移動し、index.htmlを削除します。

touch src/root.tsx
src/root.tsx
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "react-router";
 
export function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0"
        />
        <title>My App</title>
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}
 
export default function Root() {
  return <Outlet />;
}

👉 RouterProviderより上のすべてをroot.tsxに移動する

グローバルスタイル、コンテキストプロバイダーなどは、すべてのルートで共有できるようにroot.tsxに移動する必要があります。

たとえば、App.tsxが次のようになっている場合。

src/App.tsx
import "./index.css";
 
export default function App() {
  return (
    <OtherProviders>
      <AppLayout>
        <RouterProvider router={router} />
      </AppLayout>
    </OtherProviders>
  );
}

RouterProviderより上のすべてをroot.tsxに移動します。

src/root.tsx
+import "./index.css";
 
// ... その他のインポートとレイアウト
 
export default function Root() {
  return (
+   <OtherProviders>
+     <AppLayout>
        <Outlet />
+     </AppLayout>
+   </OtherProviders>
  );
}

5. クライアントエントリモジュールを追加する(オプション)

一般的なViteアプリでは、index.htmlファイルはクライアントエントリポイントとしてsrc/main.tsxを指しています。React Routerは代わりにsrc/entry.client.tsxという名前のファイルを使用します。

entry.client.tsxが存在しない場合、React Router Viteプラグインはデフォルトの非表示のものを使用します。

👉 src/entry.client.tsxをエントリポイントにする

現在のsrc/main.tsxが次のようになっている場合。

src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";
 
const router = createBrowserRouter([
  // ... ルート定義
]);
 
ReactDOM.createRoot(
  document.getElementById("root")!
).render(
  <React.StrictMode>
    <RouterProvider router={router} />;
  </React.StrictMode>
);

entry.client.tsxに名前を変更し、次のように変更します。

src/entry.client.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
 
ReactDOM.hydrateRoot(
  document,
  <React.StrictMode>
    <HydratedRouter />
  </React.StrictMode>
);
  • createRootの代わりにhydrateRootを使用する
  • <App/>コンポーネントの代わりに<HydratedRouter>をレンダリングする
  • 注:ルートを作成して<RouterProvider />に手動で渡すことはなくなりました。次の手順でルート定義を移行します。

6. ルートを移行する

React Router Viteプラグインは、routes.tsファイルを使用してルートを構成します。形式はデータルーターの定義と非常によく似ています。

👉 定義をroutes.tsファイルに移動する

touch src/routes.ts src/catchall.tsx

ルート定義をroutes.tsに移動します。スキーマは完全に一致しないため、型エラーが発生します。これは次に修正します。

src/routes.ts
+import type { RouteConfig } from "@react-router/dev/routes";
 
-const router = createBrowserRouter([
+export default [
  {
    path: "/",
    lazy: () => import("./routes/layout").then(convert),
    children: [
      {
        index: true,
        lazy: () => import("./routes/home").then(convert),
      },
      {
        path: "about",
        lazy: () => import("./routes/about").then(convert),
      },
      {
        path: "todos",
        lazy: () => import("./routes/todos").then(convert),
        children: [
          {
            path: ":id",
            lazy: () =>
              import("./routes/todo").then(convert),
          },
        ],
      },
    ],
  },
-]);
+] satisfies RouteConfig;

👉 lazyローダーをfileローダーに置き換える

src/routes.ts
export default [
  {
    path: "/",
-   lazy: () => import("./routes/layout").then(convert),
+   file: "./routes/layout.tsx",
    children: [
      {
        index: true,
-       lazy: () => import("./routes/home").then(convert),
+       file: "./routes/home.tsx",
      },
      {
        path: "about",
-       lazy: () => import("./routes/about").then(convert),
+       file: "./routes/about.tsx",
      },
      {
        path: "todos",
-       lazy: () => import("./routes/todos").then(convert),
+       file: "./routes/todos.tsx",
        children: [
          {
            path: ":id",
-           lazy: () => import("./routes/todo").then(convert),
+           file: "./routes/todo.tsx",
          },
        ],
      },
    ],
  },
] satisfies RouteConfig;

ルートの構成に関するガイドを参照して、routes.tsファイルと、ルート定義をさらに簡素化するヘルパー関数について詳細を確認してください。

7. アプリを起動する

この時点で、React Router Viteプラグインに完全に移行する必要があります。devスクリプトを更新してアプリを実行し、すべてが機能していることを確認してください。

👉 devスクリプトを追加してアプリを実行する

package.json
"scripts": {
  "dev": "react-router dev"
}

次に、先に進む前に、この時点でアプリを起動できることを確認します。

npm run dev

SSRとプリレンダリングを有効にする

サーバーレンダリングと静的プリレンダリングを有効にするには、バンドラープラグインのssrオプションとprerenderオプションを使用できます。SSRの場合、サーバービルドをサーバーにデプロイする必要もあります。詳細については、デプロイを参照してください。

react-router.config.ts
import type { Config } from "@react-router/dev/config";
 
export default {
  ssr: true,
  async prerender() {
    return ["/", "/about", "/contact"];
  },
} satisfies Config;