remix-v2-data-flow-review について
TypeScript の Remix v2 プロジェクト向けコードレビュースキルです。ルートのデータ読み込み、更新、再検証、ストリーミングを対象とし、誤検知を避けるチェックリストも備えています。
主な機能
- 01loader と action のデータフロー確認
- 02入力検証と機密フィールド漏えいの確認
- 03再検証と処理中状態の監査
- 04defer とストリーミングの確認
- 05レビュー手順と正当なパターンの提示
活用シーン
- 01Remix v2 ルートのデータ処理レビュー
- 02action の検証と成功分岐の確認
- 03再検証や遅延データの問題調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill remix-v2-data-flow-reviewインストール後、Claude Code で/remix-v2-data-flow-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/actions.mddefer-await.mdloaders.mdrevalidation.md詳細
Remix v2 データフローのコードレビュー
@remix-run/* からインポートする TypeScript のルートモジュールが対象です。標準的なパターンについては、remix-v2-data-flow を参照してください。
対象範囲
- 対象:
app/routes/配下にあるloader、action、shouldRevalidate、またはheadersをエクスポートするルートモジュール、およびuseLoaderData、useActionData、useNavigation、useFetcher、useRevalidator、<Await>を使用するコンポーネント。 - 対象外:フォームの使いやすさ(
<Form>のマークアップ、アクセシビリティ、useFetcherの UI パターン)→remix-v2-forms-reviewが担当します。ルートモジュールの規約、ファイル名、ネストされたルーティング、エラーバウンダリの配置 →remix-v2-routing-reviewが担当します。 - 想定されるインポート:サーバーユーティリティには
@remix-run/node(または@remix-run/cloudflare/@remix-run/deno)、フックとコンポーネントには@remix-run/react。
クイックリファレンス
| 問題の種類 | 参照先 |
|---|---|
| loader 内のミューテーション、バリデーション不足、サーバーフィールドの漏えい、プリミティブ値のスロー、パラメーターのチェック不足 | references/loaders.md |
未検証の FormData、成功時に redirect ではなく json を使用、エラーケースの欠落、actionData の漏えい | references/actions.md |
v1 からの useTransition の残存、保留中状態の欠落、無条件の shouldRevalidate: false、useRevalidator の誤用 | references/revalidation.md |
すでに高速なデータへの defer の使用、<Suspense> の欠落、<Await> の errorElement の欠落、ストリーミングすべきデータの await | references/defer-await.md |
レビューチェックリスト
- 初回レンダリングに必要なデータは
useEffectではなくloaderにある - loader は読み取りのみを行い、書き込みは
actionに置く -
request.formData()の結果は使用前に検証する(zod/valibot/invariant) - loader/action の戻り値は射影された DTO であり、パスワードハッシュ、トークン、または
internal_*フィールドを含まない -
useLoaderData<typeof loader>()では型アノテーション形式を使用する(as Fooではない) - 404 / 認証の早期終了では
Response(またはjson/redirect)をthrowし、通常のErrorや文字列は決して使わない - action の成功時は
redirect(...)を返す(PRG)。バリデーション失敗時はjson({ errors }, { status: 400 })を返す - action は成功とエラーの両方の分岐を処理し、無言で
return nullしない -
params.fooは使用前にinvariant/ zod でチェックする - 保留中の UI では
useNavigation()/fetcher.stateを読み取り、useTransitionは使用しない -
formMethodの比較には大文字を使う("post"ではなく"POST") -
shouldRevalidateはデフォルトでdefaultShouldRevalidateを返す。再検証の除外は限定的かつ正当化されたものにする -
defer()は少なくとも1つの Promise がストリーミングされる場合にのみ使用する(渡す前にawaitしない) - すべての
<Await>を<Suspense>でラップし、errorElementを指定する -
useRevalidator().revalidate()はフォーカス、ポーリング、SSE に限定して使用する。<Form>の送信後やfetcher.submitの直後には呼び出さない(Remix がすでに再検証します)。
有効なパターン(問題として報告しないでください)
以下は正しい Remix v2 の使用方法であり、問題として報告してはいけません。
- クライアント専用データに対する
useEffect— ローダーはサーバー側で実行されます。localStorage、windowのサイズ、IntersectionObserver、ブラウザー専用 API はuseEffectで扱います。 nullを返すloader— ローダーは正当な理由でnullを返すことがあります(例:任意のリソースが存在しない場合)。404 をthrowする必要がある場合に限り、問題として報告してください。- 型注釈としての
useLoaderData<typeof loader>()—<typeof loader>はSerializeFrom<T>に渡されるジェネリックパラメーターであり、as形式の型アサーションではありません。「安全でないキャスト」として報告しないでください。 new Response(body, init)をそのまま返す — v2 のルートでは任意のResponseを返せます。json()は使いやすくするためのラッパーであり、必須ではありません。JSON 以外の本文(バイナリ、テキスト、ストリーム)でjson()を使わないのは適切です。- アクションからの
return redirect(...)— アクションではreturn redirect(...)とthrow redirect(...)のどちらも有効です。呼び出し元の関数から抜ける必要がある場合にthrowが必須となるのは、アクションではないヘルパーから呼び出す場合だけです。 request引数なしで宣言されたloader— ローダーでは必要なものだけを分割代入できます({ params }、{ context }、または引数なしの())。未使用の引数がないことはバグではありません。- 無関係なアクションの後に親の
loaderが再検証される — これは Remix のデフォルトの動作であり、問題の兆候ではありません。shouldRevalidateが存在し、かつ誤っている場合に限り、問題として報告してください。 json({ errors }, { status: 400 })を返すアクション — これは正規のバリデーションエラーパターンです(フィールドエラーを表示したままフォームのルートをレンダリングできます)。「成功時にリダイレクトしない」というアンチパターンとは異なります。- フォーカス/ポーリング/タブ間同期のための
useRevalidator— これらはドキュメントで説明されている使用例です。<Form>の送信やfetcher.submitの直後に手動でrevalidate()を呼び出し、Remix がすでに再検証する場合に限り、問題として報告してください。 SerializeFromによって引き起こされる型の変更 — デシリアライズ後にDateがstringとして型付けされることや、Mapが{}として型付けされることは、正しいワイヤー形式の動作であり、型付けのバグではありません。
コンテキストに応じたルール
以下の問題は、該当する特定のコンテキストでのみ報告してください。
| 問題 | 以下の場合に限り報告 |
|---|---|
ローダーがない(代わりに useEffect を使用) | データがサーバー側で利用可能で、ブラウザー専用 API の読み取りではない |
loader が ORM オブジェクトをそのまま返す | オブジェクトに、レビュー担当者がスクリーンショットに載せたくないフィールド(パスワード、トークン、内部フラグ)が含まれている |
アクションが成功時に json を返す | アクションが <Form> 経由で呼び出され、URL が変更される場合。useFetcher 経由の場合は除く |
| 保留中の UI がない | 同じ画面を制御する nav.state / fetcher.state への参照がファイル内のほかの場所にない |
shouldRevalidate が false を返す | 本体に条件がない、または formAction / currentParams / nextParams を一度も参照しない |
手動での useRevalidator().revalidate() | 呼び出しが Remix 管理のミューテーション(<Form> の送信、fetcher.submit)の後に行われる場合。フォーカス/ポーリング/WebSocket の場合は除く |
defer() の使用 | defer({...}) のペイロード内のすべてのプロミスが、呼び出し前にすでに await されている |
指摘事項を作成する前の必須ゲート
以下を順番に実行してください。報告するバッチについてすべてのゲートを通過するまで、ユーザー向けの指摘事項を作成しないでください。
-
場所の証拠 — 合格: 各問題には、リポジトリ内のルートモジュールのパスと、読み取ったファイルにある行範囲または短い逐語引用(記憶や差分だけに基づく推測ではないもの)が記載されています。
export async function loader|actionへのパスが示されていないローダー/アクションの問題は報告できません。 -
免除項目の確認 — 合格: 各問題について、有効なパターン(指摘しない)の対象外である理由を1行で説明できます。特に、
useEffectがクライアント限定データを読み込んでいないこと、単独のResponseの返却が意図的に非JSONでないこと、loaderがnullを返すケースが正当な任意読み込みでないことを確認してください。 -
型注釈と型アサーションの確認 — 合格: ローダー/アクションのデータ利用に関して「安全でないキャスト」を指摘する前に、コードで使われているのが
as(アサーション)であることを確認してください。useLoaderData<typeof loader>()(注釈)でも、useActionData<typeof action>()(注釈)でもないことを確認します。ジェネリック形式は、文書化された安全な方法であり、指摘してはいけません。 -
v1からの残存コード確認 — 合格: 「保留状態の不足」を指摘する前に、ファイル内で
useTransition、transition.submission、fetcher.type、formMethod === "post"またはformMethod==='post'(小文字、空白や引用符の形式は問わない)、およびLoaderArgs/ActionArgsを検索してください。該当するものがある場合、その指摘は機能不足ではなく、v1からの移行時に残った問題です。そのように分類してください。 -
プロトコル — 合格: このレビューについて、review-verification-protocol の提出前検証チェックリストを完了しています。
参照資料を読み込むタイミング
loaderの本体、戻り値の形式、パラメーター、例外、機密フィールドの漏えいをレビューする場合 → references/loaders.mdactionの本体、FormData の検証、成功/エラー分岐、PRG リダイレクトをレビューする場合 → references/actions.mduseNavigation/useTransitionの移行、shouldRevalidate、useRevalidatorの使い方をレビューする場合 → references/revalidation.mddefer()、<Await>、<Suspense>、ストリーミングの判断をレビューする場合 → references/defer-await.md
レビュー時の確認事項
- 初回レンダリングに必要なデータは
loaderで取得されていますか。それとも、SSR と再検証を妨げるuseEffect内に残っていますか? - すべてのローダーが射影済みの DTO を返していますか。それとも、ORM の生レコード(
password、token、internal_*フィールドを含む)がブラウザーに漏えいしていますか? - すべてのアクションで、データベースにアクセスする前にスキーマを使って
request.formData()を検証していますか? - 各アクションの成功分岐で
redirect(...)していますか。これにより、更新/戻る操作が正しく動作しますか(PRG)? - 利用側のコードでは
useLoaderData<typeof loader>()(注釈)を使っていますか。useLoaderData() as Foo(アサーション)ではありませんか? - v1からの残存コード(
useTransition、transition.submission、fetcher.type、小文字のformMethod、LoaderArgs/ActionArgs)は残っていますか? shouldRevalidateはリテラルのfalseを返していますか。それともdefaultShouldRevalidateを参照し、限定的に再検証を抑制していますか?defer()は未解決の Promise が少なくとも1つ渡される場合にのみ使用されていますか。また、すべての<Await>はerrorElementを指定した<Suspense>でラップされていますか?
その他のドキュメント
- Remix v2 の標準的なデータフローパターンと v1 → v2 の差分 → remix-v2-data-flow
- 提出前の検証チェックリスト → review-verification-protocol
指摘事項を提出する前に
必須ゲート(特にゲート5)を完了し、review-verification-protocol の提出前チェックに引き続き合格する問題のみを報告してください。