Remixからのアップグレード

React Router v7は、v2に続くRemixの次のメジャーバージョンです(詳しくは、弊社の「React 19への段階的な移行」ブログ投稿をご覧ください)。

Remix v2の将来的なフラグをすべて有効にしている場合、Remix v2からReact Router v7へのアップグレードは、主に依存関係の更新を伴います。

手順2~8の大部分は、コミュニティメンバーであるJames Restallによって作成されたコードモッドを使用して自動的に更新できます。

1. 将来的なフラグの採用

👉 将来的なフラグを採用する

Remix v2アプリケーションで、既存のすべての将来的なフラグを採用します。

2. 依存関係の更新

以前はランタイム固有のパッケージ(@remix-run/node, @remix-run/cloudflareなど)を通して再エクスポートされていた「共有」APIのほとんどは、v7ではreact-routerに統合されました。そのため、@react-router/nodeまたは@react-router/cloudflareからインポートする代わりに、react-routerから直接インポートします。

-import { redirect } from "@react-router/node";
+import { redirect } from "react-router";

v7でランタイム固有のパッケージからインポートする必要があるAPIは、NodeのcreateFileSessionStorageやCloudflareのcreateWorkersKVSessionStorageなど、そのランタイムに固有のAPIのみです。

👉 コードモッドを実行する(自動化)

以下のコードモッドを使用して、パッケージとインポートを自動的に更新できます。このコードモッドは、すべてのパッケージとインポートを更新します。元に戻す必要がある場合に備えて、コードモッドを実行する前に、保留中の変更をコミットしてください。

npx codemod remix/2/react-router/upgrade

👉 新しい依存関係をインストールする

コードモッドが依存関係を更新した後、Remixパッケージを削除し、新しいReact Routerパッケージを追加するために、依存関係をインストールする必要があります。

プレリリース版である間は、package.jsonを更新して、react-routerパッケージのプレリリース版を指定する必要があります。

npm install

👉 依存関係を更新する(手動)

コードモッドを使用しない場合は、手動で依存関係を更新できます。

アルファベット順のパッケージ名の変更を表形式で表示する
Remix v2パッケージReact Router v7パッケージ
@remix-run/architect➡️@react-router/architect
@remix-run/cloudflare➡️@react-router/cloudflare
@remix-run/dev➡️@react-router/dev
@remix-run/express➡️@react-router/express
@remix-run/fs-routes➡️@react-router/fs-routes
@remix-run/node➡️@react-router/node
@remix-run/react➡️react-router
@remix-run/route-config➡️@react-router/dev
@remix-run/routes-option-adapter➡️@react-router/remix-routes-option-adapter
@remix-run/serve➡️@react-router/serve
@remix-run/server-runtime➡️react-router
@remix-run/testing➡️react-router

3. package.jsonscriptsの変更

コードモッドを使用した場合は、この手順は自動的に完了しているのでスキップできます。

👉 package.jsonのスクリプトを更新する

スクリプトRemix v2React Router v7
devremix vite:dev➡️react-router dev
buildremix vite:build➡️react-router build
startremix-serve build/server/index.js➡️react-router-serve build/server/index.js
typechecktsc➡️react-router typegen && tsc

4. routes.tsファイルの追加

コードモッドとRemix v2のunstable_routeConfigフラグを使用した場合は、この手順は自動的に完了しているのでスキップできます。

React Router v7では、app/routes.tsファイルを使用してルートを定義します。詳しくは、ルーティングに関するドキュメントをご覧ください。

👉 依存関係の更新(Remix v2のunstable_routeConfigフラグを使用している場合)

// app/routes.ts
-import { type RouteConfig } from "@remix-run/route-config";
-import { flatRoutes } from "@remix-run/fs-routes";
-import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter";
 
export default [
  // ルートの定義方法
] satisfies RouteConfig;
 

👉 routes.tsファイルの追加(Remix v2のunstable_routeConfigフラグを使用していない場合)

touch app/routes.ts

後方互換性のため、そしてファイルベースの規約を好む人のために、Remix v2で使用しているのと同じ「フラットルート」規約を、新しい@react-router/fs-routesパッケージを介して選択できます。

app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
 
export default flatRoutes() satisfies RouteConfig;

または、routesオプションを使用して設定ベースのルートを定義していた場合:

app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter";
 
export default remixRoutesOptionAdapter((defineRoutes) => {
  return defineRoutes((route) => {
    route("/", "home/route.tsx", { index: true });
    route("about", "about/route.tsx");
    route("", "concerts/layout.tsx", () => {
      route("trending", "concerts/trending.tsx");
      route(":city", "concerts/city.tsx");
    });
  });
}) satisfies RouteConfig;

vite.config.tsroutesオプションを使用していた場合は、削除してください。

export default defineConfig({
  plugins: [
    remix({
      ssr: true,
-     ignoredRouteFiles: ['**/*'],
-     routes(defineRoutes) {
-       return defineRoutes((route) => {
-         route("/somewhere/cool/*", "catchall.tsx");
-       });
-     },
    })
    tsconfigPaths(),
  ],
});

5. React Router設定の追加

👉 プロジェクトにreact-router.config.tsを追加する

以前はvite.config.tsremixプラグインに渡されていた設定は、現在react-router.config.tsからエクスポートされています。

注:この時点で、手順1で追加したv3の将来的なフラグを削除する必要があります。

touch react-router.config.ts
// vite.config.ts
export default defineConfig({
  plugins: [
-   remix({
-     ssr: true,
-     future: {/* all the v3 flags */}
-   }),
+   remix(),
    tsconfigPaths(),
  ],
});
 
// react-router.config.ts
+import type { Config } from "@react-router/dev/config";
+export default {
+  ssr: true,
+} satisfies Config;

6. vite.configへのReact Routerプラグインの追加

コードモッドを使用した場合は、この手順は自動的に完了しているのでスキップできます。

👉 vite.configreactRouterプラグインを追加する

vite.config.tsを変更して、@react-router/dev/viteから新しいreactRouterプラグインをインポートして使用します。

-import { vitePlugin as remix } from "@remix-run/dev";
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
 
export default defineConfig({
  plugins: [
-   remix(),
+   reactRouter(),
    tsconfigPaths(),
  ],
});

7. 型安全性の有効化

TypeScriptを使用していない場合は、この手順をスキップできます。

React Routerは、ルートモジュールの型をアプリのルートにある.react-router/ディレクトリに自動的に生成します。このディレクトリはReact Routerによって完全に管理されており、.gitignoreに追加する必要があります。新しい型安全性の機能の詳細については、こちらをご覧ください。

👉 .gitignore.react-router/を追加する

.react-router/

👉 tsconfig.jsonを更新する

tsconfig.jsontypesフィールドを更新して、以下を含めます。

  • includeフィールドに.react-router/types/**/*パス
  • typesフィールドに適切な@react-router/*パッケージ
  • 簡素化された相対インポートのためのrootDirs
{
  "include": [
    /* ... */
+   ".react-router/types/**/*"
  ],
  "compilerOptions": {
-   "types": ["@remix-run/node", "vite/client"],
+   "types": ["@react-router/node", "vite/client"],
    /* ... */
+   "rootDirs": [".", "./.react-router/types"]
  }
}

8. エントリファイル内のコンポーネントの名前変更

コードモッドを使用した場合は、この手順は自動的に完了しているのでスキップできます。

アプリケーションにentry.server.tsxと/またはentry.client.tsxファイルがある場合は、これらのファイルのメインコンポーネントを更新する必要があります。

app/entry.server.tsx
-import { RemixServer } from "@remix-run/react";
+import { ServerRouter } from "react-router";
 
-<RemixServer context={remixContext} url={request.url} />,
+<ServerRouter context={remixContext} url={request.url} />,
app/entry.client.tsx
-import { RemixBrowser } from "@remix-run/react";
+import { HydratedRouter } from "react-router/dom";
 
hydrateRoot(
  document,
  <StrictMode>
-   <RemixBrowser />
+   <HydratedRouter />
  </StrictMode>,
);

9. AppLoadContextの型の更新

remix-serveを使用していた場合は、この手順をスキップできます。これは、Remix v2でカスタムサーバーを使用していた場合にのみ適用されます。

React RouterはReactフレームワークとスタンドアロンのルーティングライブラリの両方として使用できるため、LoaderFunctionArgsActionFunctionArgscontext引数は、デフォルトではオプションでany型になります。ローダーとアクションの型安全性を確保するために、ロードコンテキストの型を登録できます。

👉 ロードコンテキストの型の登録

新しいRoute.LoaderArgsRoute.ActionArgs型に移行する前に、LoaderFunctionArgsActionFunctionArgsをロードコンテキスト型で一時的に拡張して、移行を容易にすることができます。

app/env.d.ts
declare module "react-router" {
  // v2で使用されていたAppLoadContext
  interface AppLoadContext {
    whatever: string;
  }
 
  // ローダーに代わりに`Route.LoaderArgs`を使用するようになったら、これを削除します
  interface LoaderFunctionArgs {
    context: AppLoadContext;
  }
 
  // アクションに代わりに`Route.ActionArgs`を使用するようになったら、これを削除します
  interface ActionFunctionArgs {
    context: AppLoadContext;
  }
}

型を登録するためにdeclare moduleを使用することは、モジュール拡張と呼ばれる標準的なTypeScriptテクニックです。これは、tsconfig.jsonincludeフィールドに含まれる任意のTypeScriptファイルで行うことができますが、アプリディレクトリ内の専用のenv.d.tsを使用することをお勧めします。

👉 新しい型の使用

新しい型生成を採用したら、LoaderFunctionArgs/ActionFunctionArgsの拡張を削除し、代わりにRoute.LoaderArgsRoute.ActionArgsからcontext引数を使用します。

app/env.d.ts
declare module "react-router" {
  // v2で使用されていたAppLoadContext
  interface AppLoadContext {
    whatever: string;
  }
}
app/routes/my-route.tsx
import type { Route } from "./+types/my-route";
 
export function loader({ context }: Route.LoaderArgs) {}
// { whatever: string }  ^^^^^^^
 
export function action({ context }: Route.ActionArgs) {}
// { whatever: string }  ^^^^^^^

おめでとうございます!これでReact Router v7に移行できました。アプリケーションを実行して、すべてが期待通りに動作していることを確認してください。