remix-v2-error-boundaries-review について
TypeScript の Remix v2 プロジェクト向けエラーバウンダリのレビュースキルです。ルートのエラー処理や v1 移行時の残存要素を確認し、境界の構造、Response の throw、ルート境界、エラー報告を扱います。
主な機能
- 01ErrorBoundary の宣言とエラー分類を確認
- 024xx・5xx の throw 方法をレビュー
- 03ルート境界のドキュメント構造を確認
- 04CatchBoundary など v1 の残存要素を検出
- 05免除条件と重大度の指針を提示
活用シーン
- 01Remix ルートのエラーバウンダリレビュー
- 024xx Response が境界に到達するか確認
- 03Remix v1 のエラー処理の残存箇所を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill remix-v2-error-boundaries-reviewインストール後、Claude Code で/remix-v2-error-boundaries-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/boundary-shape.mdroot-boundary.mdthrow-response.mdv1-holdovers.md詳細
Remix v2 のエラーバウンダリーのコードレビュー
@remix-run/* からインポートする TypeScript のルートモジュールが対象です。このトピックに対応する関連ナレッジスキルはありません。標準的な考え方を以下にまとめ、詳しくは references/ で説明します。
v2 のバウンダリーモデル(最初にお読みください)
Remix v2 では、v1 の CatchBoundary と ErrorBoundary が、ルートモジュールの 単一の ErrorBoundary エクスポートに統合されました。フレームワークは、スローされた Response(例:throw new Response(...)、throw json(...))と、スローされたランタイムエラー(loader/action/render の例外)の 両方に対してこれを呼び出します。バウンダリー内では useRouteError() フックで値を取得し、次の順序で型を絞り込みます。
isRouteErrorResponse(error)→Responseがスローされています。error.status、error.statusText、error.dataを参照します。error instanceof Error→ 実際のランタイムエラーです。error.messageを参照します。- それ以外 → 不明な値がスローされています。汎用のフォールバックを表示します。
バウンダリーは props を受け取りません。CatchBoundary、useCatch、future.v2_errorBoundary フラグはすべて廃止されています。これらが見つかった場合は、いずれも v1 の名残です。エラーは最も近い ErrorBoundary で表示され、存在しない場合はルートまでバブルアップします。ルートバウンダリーではドキュメント全体が再マウントされるため、<Meta />、<Links />、<Scripts /> をレンダリングする必要があります。バウンダリーに到達するのは、loader/action の結果が スローされた場合のみです。4xx ステータスで return json(...) を返すのは、成功した loader であり、エラーではありません。サーバー側のランタイムエラーは、任意の entry.server.tsx の handleError エクスポートにも渡されます(スローされた Response は渡されません)。
クイックリファレンス
| 問題の種類 | リファレンス |
|---|---|
ルートの ErrorBoundary がない、バウンダリーへの props の指定、instanceof Error だけによる型の絞り込み、isRouteErrorResponse だけによる型の絞り込み | references/boundary-shape.md |
4xx/5xx の throw ではなく return、error.data の握りつぶし、文字列のスロー、handleError がない | references/throw-response.md |
ルートバウンダリーがない、<Meta />/<Links />/<Scripts /> のないルートバウンダリー、ルートバウンダリー内での useLoaderData() | references/root-boundary.md |
CatchBoundary のエクスポート、useCatch のインポート、v2_errorBoundary future フラグ | references/v1-holdovers.md |
レビューチェックリスト
-
ErrorBoundaryが props なしのexport function ErrorBoundary()として宣言されている - エラーを props や
useCatch()ではなく、useRouteError()で取得している - 型の絞り込みでは、最初に
isRouteErrorResponse(error)を確認し、次にerror instanceof Errorを確認してから、フォールバックを使用している -
error.dataを JSX に渡す前に、型を確認・絞り込みし、防御的にレンダリングしている - loader/action の 4xx / 5xx では、
Response/jsonにreturnではなくthrowを使っている - スローされる可能性のあるルートが、それぞれ独自の
ErrorBoundaryをエクスポートしている(ウィジェットのエラーで親を巻き込まない) - ルートの
app/root.tsxが<Meta />、<Links />、<Scripts />をレンダリングするErrorBoundaryをエクスポートしている - ルートのデータを参照する際、ルートバウンダリーでは
useLoaderData()ではなくuseRouteLoaderData("root")を使用している -
CatchBoundaryのエクスポート、useCatchのインポートがなく、remix.config.jsにfuture.v2_errorBoundaryがない -
entry.server.tsxがhandleErrorをエクスポートし、ランタイムエラーをエラーレポーターに渡している -
handleErrorがスローされたResponseも渡されると想定していない(渡されません) - スローされる値が
Response/json/Errorのインスタンスであり、プレーンな文字列や POJO ではない
有効なパターン(指摘しないでください)
以下は正しい Remix v2 の使用方法であり、問題として報告してはいけません。
- 親から意図的に継承する、
ErrorBoundaryのないルート — 境界は上位へと連鎖します。子ルートではErrorBoundaryを省略し、親(またはルート)がフォールバックをレンダリングできます。そのルートでユーザーに固有のエラー UX を処理しており、かつ親の境界では対応できない場合にのみ指摘してください。 - loader/action から
throw new Response(...)またはthrow json(...)を使用する — 404/401/403 などを通知する標準的な方法です。これは「制御フローに例外を使用する」ことではなく、文書化された v2 の仕様です。 isRouteErrorResponse(error)のみで型を絞り込む — そのルートがResponseしか throw せず、レンダリング時にクラッシュするリスクもないことが明らかな場合は許容されます。重要度は最大でも ADVISORYです。多層防御としてinstanceof Error分岐の追加を提案し、バグとして指摘しないでください。useRouteError()を呼び出さないErrorBoundary— 意図的に静的な「問題が発生しました」フォールバックをレンダリングする場合は有効です(例:エラーの詳細を表示しないマーケティングページ)。useRouteLoaderData("root")を呼び出してundefinedを取得するルートのErrorBoundary— 文書化された防御的パターンです(ルートの loader が throw した可能性があります)。undefinedの処理を「デッドコード」として指摘しないでください。request.signal.abortedの場合に早期リターンするhandleError— 文書化されたノイズフィルターであり、エラーの握りつぶしではありません。- throw された
Responseを処理しないhandleError— フレームワークの仕様上、handleErrorが呼び出されるのはランタイムエラーの場合のみです。Responseの処理がないのは正しく、欠落ではありません。 - 裸のフラグメントを返す(
<html>/<body>がない)ネストされたErrorBoundary— ドキュメントを管理するのはルートの境界のみです。ネストされた境界は親レイアウトの内部にレンダリングされるため、ドキュメントタグを含めてはいけません。
重要度のガイダンス
コードベースに別の尺度が文書化されていない限り、以下をデフォルトとして使用してください。
| パターン | デフォルトの重要度 |
|---|---|
v2 のコードベースにおける CatchBoundary エクスポート、または useCatch のインポート | BLOCKER(ビルドを壊す、またはデッドコード) |
ルートの ErrorBoundary に <Scripts /> がない | BLOCKER(エラーページで行き詰まる) |
({ error }) という v1 のプロパティシグネチャを持つ ErrorBoundary | WARN(実行時に気づかれず undefined になる) |
4xx に対して throw の代わりに return json(...) を使用する | WARN(boundary が呼び出されない) |
レンダリング時にクラッシュするリスクがあるルートで instanceof Error 分岐がない | WARN |
Response のみを扱うルートで instanceof Error 分岐がない | ADVISORY |
ルートの boundary で useLoaderData()(useRouteLoaderData ではなく)を使用する | WARN(潜在的なループ) |
entry.server.tsx に handleError がない | ADVISORY(可観測性の不足であり、バグではない) |
指摘を作成する前の必須ゲート
順番に実施してください。報告対象のバッチについて、すべてのゲートを通過するまで、ユーザー向けの指摘を作成してはいけません。
-
場所の証拠 — 合格条件: 各問題について、リポジトリ内のルートモジュールのパス(または
app/root.tsx、app/entry.server.tsx)と、行の範囲または実際に読んだファイルからの短い引用(記憶や差分だけに基づく推測ではないもの)を記載します。app/root.tsxのパスを示さずに「ルートの境界が間違っています」とする指摘は報告できません。 -
適用除外の確認 — 合格条件: 各問題について、有効なパターン(指摘しないもの)の対象ではない理由を、1行で説明できます。特に、
ErrorBoundaryがないのは親の境界へ意図的にカスケードさせているためではないことを確認してください。また、isRouteErrorResponseのみで絞り込んでいる箇所が、Response しか throw しないことが明確なルートではないことも確認してください(その場合は ADVISORY に格下げします)。 -
v1 と v2 のマーカー確認 — 合格条件: 指摘を作成する前に、ルートモジュール(および横断的な問題についてはリポジトリ全体)で
CatchBoundary、useCatch、v2_errorBoundary、ErrorBoundary({ error、ErrorBoundary({errorを grep します。いずれかが見つかった場合、その指摘は v1 の残存コードであり、(references/v1-holdovers.md を読み込んだうえで)そのようにラベル付けする必要があります。「エラーハンドリングの不足」のような一般的な問題として扱ってはいけません。見つからない場合、コードは v2 の形式であり、指摘は v2 の正確性に関するものです。 -
プロトコル — 合格条件: このレビューについて、レビュー検証プロトコルの「報告前検証チェックリスト」を完了しています。
レビューの確認事項
- throw が発生し得るすべてのルート(loader、action、render)に、適切なレベルの
ErrorBoundaryがありますか?復旧 UI が重要な場合はローカルに、意図的なカスケードの場合は親またはルートに配置されていますか? - 各
ErrorBoundaryはuseRouteError()を呼び出し(useCatch()や props ではなく)、最初にisRouteErrorResponseで絞り込んでいますか? - 4xx / 5xx の制御フローでは、境界が実際に作動するように
returnではなくthrowを使用していますか? app/root.tsxは<Meta />、<Links />、<Scripts />を含むErrorBoundaryをエクスポートし、useRouteLoaderData("root")を防御的に使用していますか?- v1 のマーカー(
CatchBoundary、useCatch、v2_errorBoundary、({ error })の props シグネチャ)が残っていますか? - 実行時エラーのオブザーバビリティのために
entry.server.tsxにhandleErrorがあり、正しい契約(Response を扱わない)に従っていますか?
参考ドキュメント
ErrorBoundaryのエクスポート形式、フックの使用方法、絞り込みをレビューする場合 → references/boundary-shape.md- throw された
Response/jsonのパターン、handleError、returnとthrowの違いをレビューする場合 → references/throw-response.md app/root.tsxの境界の構成をレビューする場合 → references/root-boundary.md- v1 の残存コード(
CatchBoundary、useCatch、v2_errorBoundary)を検出する場合 → references/v1-holdovers.md - Remix v2 の ErrorBoundary ドキュメント:https://remix.run/docs/en/main/route/error-boundary
- Remix v2 のエラーハンドリングガイド:https://remix.run/docs/en/main/guides/errors
- Remix v2 の
entry.server/handleErrorドキュメント:https://remix.run/docs/en/main/file-conventions/entry.server