React Router v7 移行で typecheck が壊れる4つの原因(Remix v2 → v7)

はじめに
React Router v7 に移行すると、
build は通るのに typecheck が壊れます。
原因はコードではなく、
v7 の仕様変更と型生成方式の変更です。
React Router v7 移行でこんなことありませんか?
AIに「Remix v2 から React Router v7 に移行して」と頼んだ。55ファイルのインポートを数分で書き換えてくれた。ビルドも通った。「完了」と思って npm run typecheck を実行すると、エラーが並んだ。
コードの置き換え自体は正しかった。問題はその後だ。ローカルのAIエージェントにエラーを解消させようとすると、ファイルを1つ修正するたびに権限確認のダイアログが止まらなくなった。
.envの漏洩インシデントが記憶に新しい今、エージェントへの権限確認を軽視できない。その確認ストップが、開発を凍りつかせた。
この記事をお勧めしない人
- typecheckを省略して「ビルドが通ればいい」と割り切れる人
- ローカルAIエージェントの権限を無制限に設定していて、確認ストップが気にならない人
- React Router v7 への移行を当面予定していない人
もし一つでも当てはまらないなら、読み進める価値があるかもしれません。
AIエージェントが止まり続ける移行後のtypecheck
- typecheckエラーが4点あることで、エージェントは「直した」と判断しては次のエラーに移り、ファイルへのアクセス確認が繰り返され開発が止まる
- 「あと1個で終わる」という錯覚の中でコンテキストウィンドウが満杯になり、それまでの修正の意図が追えなくなる
- 権限を広げてエージェントに任せれば速く終わるとわかっていても、過去のインシデントを思い出して判断が鈍る
移行前に4点を知っておくという手順
- この記事を読めば、4つのtypecheck問題を移行前に把握でき、AIエージェントに渡す前に手動で対処できる
- 具体的には、修正コードをそのままコピーできる状態で、移行チェックリストとして使える設計図を手に入れられる
- この記事の内容は、このブログ自身(React Router × Cloudflare Edge、802テスト全パス)で本番検証済みの一次情報であり、公式ドキュメントには記載のない実践知です
このブログもそうでした
このブログ開発中、まさにこの4つの問題で作業が止まりました。権限を絞りながらAIエージェントとデバッグするたびにダイアログが並び、移行前に把握しておけばよかったと痛感しました。
この記事で、4点の問題と修正コードをそのまま持ち帰れるように書きました。
さらに深掘りして、ローカルAI開発フローにおける権限設計全体を知りたい方は、その視点からも考察を確認できます。
📝 概要
React Router v7(2024年11月リリース)は Remix v2 との互換性を意識して設計されていますが、移行後に npm run typecheck を実行すると型エラーが発生する箇所が4点あります。いずれも「コードのバグ」ではなく【APIの仕様変更・型定義の消失】が原因です。
本記事では、Cloudflare Edge ランタイム(Workers)で稼働するブログの移行作業で実際に遭遇した4つの問題と、それぞれの即効性のある修正コードを記録します。
発生環境
- 【フレームワーク】: React Router v7.13.1(移行前: Remix v2)
- 【ホスティング】: Cloudflare Pages / Workers
- 【ビルドツール】: Vite
- 【言語】: TypeScript
- 【その他】: Windows 11、Node.js 20系
4つのtypecheck問題と修正コード
⚠️ 問題1: `json()` ヘルパーの廃止
【エラーメッセージ】
Module '"react-router"' has no exported member 'json'.【原因】
React Router v7 では Single Fetch がデフォルトになったことで、json() ヘルパーが react-router から削除されました。Remix v2 では import { json } from "@remix-run/cloudflare" が使えましたが、v7 ではどのパッケージからも json をインポートできません。
【修正コード】
data を json という名前でエイリアスインポートすることで、既存コードへの変更を最小化できます。
- import { json } from "@remix-run/cloudflare";
+ import { data as json } from "react-router";data() は json() と同じシグネチャを持つため、呼び出し箇所の変更は不要です。json({ message: "ok" }, { status: 200 }) がそのまま動作します。
⚠️ 問題2: `react-dom/server.browser` の型定義不在
【エラーメッセージ】
Could not find a declaration file for module 'react-dom/server.browser'.【原因】
Cloudflare Workers(Edge Runtime)では Node.js の http モジュールが使えないため、renderToReadableStream が含まれる react-dom/server.browser を使う必要があります。Remix v2 では @remix-run/cloudflare がこのモジュールの型定義を補完していましたが、@react-router/cloudflare への移行後に型定義が消えます。
react-dom/server.browser の型定義問題は Cloudflare Edge 固有の問題であり、公式ドキュメントへの記載はありません。
【修正コード】
app/react-dom-server-browser.d.ts をプロジェクトルートに手動作成します。
// app/react-dom-server-browser.d.ts
declare module "react-dom/server.browser" {
export { renderToReadableStream } from "react-dom/server";
}このファイルは tsconfig.json の include に含まれるディレクトリに配置するだけで自動的に認識されます。追加の設定変更は不要です。
⚠️ 問題3: tsconfig の `types` エントリ変更
【エラーメッセージ(移行前の残骸が原因)】
Cannot find type definition file for '@remix-run/dev'.【または、@react-router/dev に置き換えた場合】
Could not resolve entry point for package '@react-router/dev'.【原因】
Remix v2 では tsconfig.json の compilerOptions.types に @remix-run/dev を指定することで、ルートモジュールの型定義を注入していました。しかし @react-router/dev にはトップレベルの型エントリが存在しないため、types に指定しても解決しません。
React Router v7 では、型定義は .react-router/types/ ディレクトリに自動生成される方式に変わりました。
【修正コード】
// tsconfig.json
{
"compilerOptions": {
- "types": ["@remix-run/dev", "vite/client"]
+ "types": ["vite/client"]
},
"include": [
"**/*.ts",
"**/*.tsx",
+ ".react-router/types/**/*"
]
}@remix-run/dev を types から削除し、.react-router/types/**/* を include に追加します。npm run dev または npm run build を一度実行すると .react-router/types/ が生成されます。
⚠️ 問題4: `ServerRouter` の `abortDelay` prop 廃止
【エラーメッセージ】
Property 'abortDelay' does not exist on type 'IntrinsicAttributes & ServerRouterProps'.【原因】
entry.server.tsx で使用していた RemixServer コンポーネントは、React Router v7 では ServerRouter に変わります。それに伴い、abortDelay prop が削除されました。ストリーミングのタイムアウト制御は renderToReadableStream の signal オプションで直接行う方式になっています。
【修正コード】
// app/entry.server.tsx
- import { RemixServer } from "@remix-run/react";
+ import { ServerRouter } from "react-router";
const body = await renderToReadableStream(
- <RemixServer context={remixContext} url={request.url} abortDelay={5000} />,
+ <ServerRouter context={routerContext} url={request.url} />,
{ signal: controller.signal }
);タイムアウトを設定する場合は、AbortController に setTimeout を組み合わせて controller.abort() を呼び出す形で制御します。
🎓 学んだこと・まとめ
技術的な学び
- 【Single Fetch の導入が API 変更の主因】:
json()廃止は v7 の設計変更の中心にあり、data()へのエイリアスが最もコスト低く対処できる - 【Edge Runtime 固有の型問題は公式が補完しない】:
react-dom/server.browserの型定義不在は Cloudflare Edge 特有の問題であり、自前の.d.tsで対処する必要がある - 【tsconfig の型注入方式が変わった】:
types配列への指定からincludeへのパス追加へ。「置き換えればいい」ではなく「削除して別の方法で追加」が正解
移行前の対処チェックリスト
AIエージェントに渡す前に、以下を手動で処理しておくことで権限確認ストップを最小化できます。
-
jsonインポートをimport { data as json } from "react-router"に置換 -
app/react-dom-server-browser.d.tsを作成 -
tsconfig.jsonのtypesから@remix-run/devを削除、includeに.react-router/types/**/*を追加 -
entry.server.tsxのabortDelayprop を削除
今後のベストプラクティス
typecheckを通すこと自体が目的ではありません。ローカルAI開発において、「AIエージェントが触るファイルを最小化する」ための事前準備として、移行前に手動で地雷を踏み抜いておくことが価値を持ちます。4点を把握した状態で移行を始めれば、エージェントへの権限確認は最小限で済みます。
同じような問題で困っている方の参考になれば幸いです!
なお、typecheckを通した後には wrangler pages dev 固有の問題が待っています。bare-import警告とWindowsキャッシュロックへの対処法を移行計画に最初から組み込むことで、「完了」報告後に予期せぬ作業が発生する事態を防げます。
