Remix v2 からReact Router v7 へupgradeをする
初めに
お疲れ様です。gontaです。
表題の通り、immedioのプロジェクトの一つでRemix v2 から React Router V7にアップデートの対応をしました。
基本はドキュメント通りに対応をしたらいいのですが、ところどころ導入をしているライブラリによってうまくいかないところがあったので、そちらについても記載をしていきます。
前提
下記のversionを使っていました。
- "react": "^18.2.0",
- "react-dom": "^18.2.7",
- "@remix-run/node": "^2.11.1",
- "@remix-run/react": "^2.11.1",
- "@remix-run/serve": "^2.11.1",
- "@sentry/remix": "^8.32.0",
- "remix-routes": "^1.7.7",
- "remix-utils": "^7.7.0",
- "@remix-run/dev": "^2.11.1",
codemod を実行する
下記を実行して全体のpackageを変更します。
npx codemod remix/2/react-router/upgrade
上記を実行をすると、@remix-run/
のpackageが@react-router/
に変わります。
^7.0.0
のversionがinstallをされると思うので、下記で最新のversionをinstallします。い6/10時点でlatestで実行をすると、^7.6.2
でinstallをされています。
bun i @react-router/serve@latest @react-router/node@latest react-router@latest @react-router/dev@latest @react-router/dev@latest
package.jsonの中身をinstallする
package.json の scripts を変更する
これも基本ドキュメント通りです。
細かい点はプロジェクトに合わせてカスタマイズをしてください。
"dev": "react-router dev",
"build": "react-router build",
"start": "react-router-serve build/server/index.js",
"typecheck": "react-router typegen && tsc --build --noEmit",
routes.ts ファイルを追加する
immedioではremix-flat-routesを使っており、ドキュメントには記載がありません。
routes.ts内で使用するRemixのroutes設定オプションのアダプターをinstallします。
bun i @react-router/remix-config-routes-adapter
こちらを参考にさせていただきました。
import { remixConfigRoutes } from "@react-router/remix-config-routes-adapter";
import { flatRoutes } from "remix-flat-routes";
export default remixConfigRoutes((defineRoutes) =>
flatRoutes("routes", defineRoutes),
);
React Router 構成を追加する
こちらも基本ドキュメント通りです。
react-router.config.ts を作成
こちらもドキュメント通りに対応します。
touch react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
} satisfies Config;
vite.config.ts
- ignoredRouteFiles、routesを削除
- v3 futureを削除
下記は修正後の一部実装です。
import { reactRouter } from "@react-router/dev/vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { remixDevTools } from "remix-development-tools";
import { remixRoutes } from "remix-routes/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
remixDevTools(),
reactRouter(), // routes.tsを見て解釈をしているいるっぽい
tsconfigPaths(),
remixRoutes(),
],
// ...
});
型安全性を有効にする
こちらもドキュメント通りに対応します。
.react-router/ を .gitignore に追加する
.react-router/
tsconfig.tsを更新する
こちらも基本はドキュメント通りに対応します。
- include フィールドの .react-router/types/**/* パス
- types フィールドの適切な @react-router/* パッケージ
- 相対インポートを簡略化するための
rootDirs
を追加
追加対応でpluginsに@react-router/dev
を追加をし、routesが変更される度に型情報の自動生成が行われるようにします。下記を参考にさせていただきました。
{
"include": [
/* ... */
+ ".react-router/types/**/*"
],
"compilerOptions": {
- "types": ["@remix-run/node", "vite/client"],
+ "types": ["@react-router/node", "vite/client"],
/* ... */
+ "rootDirs": [".", "./.react-router/types"],
+. "plugins": [{ "name": "@react-router/dev" }], //
}
}
これで、下記を実行をする
react-router typegen
そして下記がimportをすることができるようになります。今回はこちらのRouteへの変更処理は行いませんでした。
import { Route } from "./+types";
エントリファイル内のコンポーネントの名前を変更する
こちらもドキュメント通りです。
codemodを実行したときにコンポーネントの名前は自動で変わっていました。
entry.client.tsxを調整
- import { RemixBrowser } from "@remix-run/react";
+ import { HydratedRouter } from "react-router/dom";
hydrateRoot(
document,
<StrictMode>
- <RemixBrowser />
+ <HydratedRouter />
</StrictMode>,
);
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} />,
自分はabortDelayが型エラーが出ていた。
react router v7でfutureフラグが全て有効化をされるとのことで、ABORT_DELAY
も置き換えます。
- const ABORT_DELAY = 5000;
+ // 5 秒後に保留中のすべての Promise を拒否/キャンセルします
+ export const streamTimeout = 5000;
// ...
function handleBrowserRequest(/* ... */) {
return new Promise((resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
- abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
/* ... */
},
onShellError(error: unknown) {
/* ... */
},
onError(error: unknown) {
/* ... */
},
}
);
- setTimeout(abort, ABORT_DELAY);
+ // React レンダラーを 6 秒後に自動的にタイムアウトさせます。これにより、
+ // React が拒否された境界の内容をフラッシュするのに十分な時間が確保されます。
+ setTimeout(abort, streamTimeout + 1000);
});
}
動作確認
ドキュメントに合わせて実装をできたので、起動をしてみるとエラーが出ました。
エラーログを見ると、react-router-domがうまくいっていないみたいです。
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
✘ [ERROR] No matching export in "node_modules/react-router/dist/development/index.mjs" for import "UNSAFE_logV6DeprecationWarnings"
node_modules/react-router-dom/dist/index.js:13:36:
13 │ ..., UNSAFE_logV6Depreca...
╵ ~~~~~~~~~~~~~~~~~~~
こちらを参考にreact-router-dom
をinstallする
bun i react-router-dom@latest
この時点で型エラーは大量に出ると思うのですが、ランタイムでエラーが出ないことを確認をしました🎉
型エラーや、ライブラリの細かい対応をする
ここまででローカルを起動をして画面を確認することができました。
おそらく動作は基本的に問題ないと思うのですが、下記を対応をしています。
- 導入をしているライブラリの非互換で型エラーが出ている。
- React Router v7へのライブラリのupdate
Sentry
react router v7に移行に合わせて@sentry/remix
に移行をしました。
Write a migration path from @sentry/remix to @sentry/react-router
githubのissueが出ている。@sentry/remix
をつかって、いずれは@sentry/react-router
を使うということのような気がする
下記を参考
こちらを参考に@sentry/remix
で実装調整
各所のimportを修正する。
- import * as Sentry from "@sentry/remix";
+ import * as Sentry from "@sentry/react";
Sentry.initのintegrations
を修正をする。
+ import {
+ createRoutesFromChildren,
+ matchRoutes,
+ useLocation,
+ useNavigationType,
+ } from "react-router";
// ...
Sentry.init({
// ...
integrations: [
- Sentry.browserTracingIntegration({
- useEffect,
- useLocation,
- useMatches,
- }),
+. Sentry.reactRouterV7BrowserTracingIntegration({
+. useEffect,
+. useLocation,
+. useNavigationType,
+. createRoutesFromChildren,
+. matchRoutes,
}),
// ...
],
});
remix-routesをsafe-routesに更新
immedioでは型安全なルーティングを実現をするために、remix-routesを導入をしていました。
ただ、今回のバージョンアップに合わせて、$params
の型が| undefind
のunion型になってしまっている。
こちらgithubを確認をするとremix-routesはsafe-routesに改名をされ、upgradeへのリンクもありましたのでこれどおりに対応をします。
remix-routes has been renamed to safe-routes. If you are looking for the documentation of remix-routes, please refer to here.
Please refer to the upgrading guide if you are upgrading from remix-routes.
先にpackageのsafe-routes
をinstallします。
bun uninstall remix-routes && bun i safe-routes
この後に自分は全てエディタの一括置換でsafe-routes
に置き換えました
vite.config.tsを編集します。
import { reactRouter } from "@react-router/dev/vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";
- import { remixRoutes } from 'remix/vite';
+ import { safeRoutes } from 'safe-routes/vite';
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
reactRouter(),
tsconfigPaths(),
sentryVitePlugin({
org: "immedio",
project: "immedio-box",
}),
- remixRoutes()
+ safeRoutes(),
],
//
//
});
package.jsonのtypecheckを修正をします。
- "typecheck": "react-router typegen && tsc --build --noEmit"
+ "typecheck": "react-router typegen && safe-routes typegen && tsc --build --noEmit"
下記を実行します。
bun run typecheck
すると$params
が非推奨になっていることが確認をできました。
こちらはそのまま利用をできるところは一旦そのままにして別タスクで対応をする判断をしました。
remix-utilsのversionを上げる
jsonHashを使われていたところが各所でneverを返すようになっていました。
v8からreact router v7に対応をしている様子なのでversionを最新に上げました。
^7.7.0 => ^8.7.0 に上げました。
bun i remix-utils@latest
これでjsonHashでnever型を返さないようになりました。
注意点として、jsonHashを使うとDate型がstring型に変わっていたのですが、そのままDate型として返ってくるのでこの点の修正を対応をしました。
一旦終了
お疲れ様です!
私はこちらの対応で今回のプロジェクトでは型エラーも各所の実行も問題がないことを確認しました。
後回しにしたこと
$paramsが非推奨になるので修正
各actionとloaderでRoute
で下記のように$paramsを使わない実装に修正できそうです。
- import type { ActionFunctionArgs } from "react-router";
- import { $params } from "safe-routes";
+ import type { Route } from "./+types";
export async function action({ params }: Route.ActionArgs) {
const { bookmarkId } = params;
// ...
}
- import type { LoaderFunctionArgs } from "react-router";
- import { $params } from "safe-routes";
+ import type { Route } from "./+types";
export async function loader({ request, params }: Route.LoaderArgs) {
const { id: contentId } = params;
// ...
}
Route.ComponentProps の対応
コンポーネントで使っているuseParams
についても下記のようにしてより型安全に実装ができるようにする
- import { useParams } from "react-router";
+ import type { Route } from "./+types";
- export default function Contents() {
+ export default function Contents({ params }: Route.ComponentProps) {
- const params = useParams();
- const { id } = $params("/contents/:id", params);
+ const { id } = params;
}
最後に
ここまでお読みいただきありがとうございました。
基本がドキュメントに沿った実装にはなりましたが、細かいライブラリを導入でうまくいかない点があったので勉強になりました。
まだRemix v2を使っている企業様もあると思いますが、React Router v7 へupgradeをするときに、こちらの記事が少しでもお役に立てれば幸いです。
Discussion