mcpField
R
remix-v2-perf-ssr-review

remix-v2-perf-ssr-review

github.com/existential-birdsテスト・品質8540 回インストール11 週間前に更新
リポジトリ

remix-v2-perf-ssr-review について

Remix v2 の TypeScript ルートモジュールを対象とする、パフォーマンスと SSR のレビュー用スキルです。キャッシュ、サーバーとクライアントの境界、水和、プリフェッチ、アセット読み込みを扱い、指摘前に文脈を確認します。

主な機能

  1. 01キャッシュヘッダーと Cookie 変化の確認
  2. 02サーバー・クライアント間の境界確認
  3. 03SSR の水和リスクの特定
  4. 04プリフェッチとストリーミングデータの点検
  5. 05画像・フォント・スタイルシート読み込みの確認

活用シーン

  1. 01ルートのキャッシュ方針のレビュー
  2. 02サーバーコード漏えいリスクの確認
  3. 03レンダリングに起因する水和不一致の特定
  4. 04プリフェッチとアセット読み込み設定の点検

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/existential-birds/beagle --skill remix-v2-perf-ssr-review

インストール後、Claude Code で/remix-v2-perf-ssr-reviewを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
assets.md
caching-headers.md
hydration.md
prefetch-streaming.md
server-client-split.md

詳細

Remix v2 のパフォーマンス/SSR コードレビュー

@remix-run/* からインポートする TypeScript のルートモジュールが対象です。標準的なパターンについては、remix-v2-perf-ssr を参照してください。

クイックリファレンス

問題の種類参照
headers エクスポートの欠落、安全でない public キャッシュ、子ルートによる親のヘッダーの破棄、Vary: Cookie の欠落、Set-Cookie と public の併用references/caching-headers.md
.server.ts なしでのサーバーライブラリのインポート、process.env.SECRET_* の漏えい、.server.ts の代わりに typeof window を使用references/server-client-split.md
レンダリング時の new Date()、キーでの Math.random()、明示的なロケールなしでのロケール形式の出力、useId() の欠落、無差別な suppressHydrationWarningreferences/hydration.md
すべてのリンクへの prefetch="render" の指定、高速なデータへの defer の使用、<Await> を囲む <Suspense> の欠落、副作用のあるルートへのプリフェッチreferences/prefetch-streaming.md
信頼できないデータでの dangerouslySetInnerHTML、loading="lazy" の欠落、links によるプリロードの欠落、body へのスタイルシートの挿入references/assets.md

レビューチェックリスト

  • データを提供するルートは headers をエクスポートする(no-store の場合も同様)
  • 個人向けデータを提供する子ルートは、それぞれ独自の headers をエクスポートする(そうしないと、親ルートのポリシーが暗黙的に継承されます)
  • 認証済みまたは Cookie を含むレスポンスに Cache-Control: public を設定しない
  • キャッシュの判断がセッションに依存する場合は Vary: Cookie を設定する
  • サーバー専用ライブラリ(prisma、bcrypt、node:fs、jsonwebtoken)は *.server.ts または app/.server/ に配置する
  • シークレット環境変数(process.env.STRIPE_SECRET_KEY など)は、loader/action または .server モジュール内でのみ読み取る
  • クライアントに公開する環境変数は window.ENV にホワイトリスト形式で登録し、生の process.env は使わない
  • .server.ts の代わりに typeof window === "undefined" を使用しない(ツリーシェイキングの信頼性が低いため)
  • JSX のレンダリングパスに new Date()、Math.random()、Date.now()、crypto.randomUUID() を含めない
  • ロケール形式での出力(toLocaleDateString、Intl.DateTimeFormat)には明示的なロケールを渡す
  • ID を生成するコンポーネントでは Math.random() やカウンターではなく useId() を使う
  • suppressHydrationWarning は単一の要素に限定し、その理由を説明するコードコメントを付ける
  • <Link prefetch="render"> は、リストではなくファーストビューに表示される重要なナビゲーションに限定する
  • <PrefetchPageLinks> の対象に、副作用(分析、変更)を伴う loader を持つルートを指定しない
  • すべての <Await> を <Suspense> で囲み、errorElement を指定する
  • defer() は実際に遅いデータ(約 50ms 超)にのみ使い、高速なデータは await する
  • ファーストビューより下の画像には loading="lazy" と width/height を指定する
  • 重要なフォント/CSS は body に挿入せず、links エクスポートでプリロードする

有効なパターン(問題として報告しない)

以下は Remix v2 の正しい使い方であり、問題として報告してはいけません。

  • 意図的にキャッシュを無効にしている場合の、headers エクスポートがないルート — 認証済みダッシュボード、アカウントページ、すでに no-store を返すレイアウトでラップされたルートでは、headers を省略しても問題ありません。headers がなく、キャッシュ可能な公開コンテンツを提供する場合に限り報告してください。
  • useEffect 内の new Date() — ハイドレーション後にクライアントのみで実行されるため、SSR との不一致は発生しません。エフェクト内の Date.now()、Math.random()、crypto.randomUUID() も同様です。
  • イベントハンドラー内の Math.random() / new Date() — ハンドラーはハイドレーション後に実行されます。レンダリング時に値が使用される場合に限り報告してください。
  • 明確なコメントが添えられた単一の <time>(または類似の)要素に対する suppressHydrationWarning — クライアント側で整形される絶対タイムスタンプのように、値が異なることが既知の場合に使える、限定的で許容される回避策です。大きなサブツリーをラップする親要素に適用されている場合、または説明がない場合に限り報告してください。
  • クライアント専用ライブラリの .client.ts ファイル — window を参照する Stripe.js、マップウィジェット、チャートライブラリは、慣例として *.client.ts に配置します。ファイル拡張子を問題として報告しないでください。
  • 追加の文字列を付加した useId() — `${id}-input` は複数要素コンポーネント向けに文書化されたパターンです。「安定しない ID」として報告しないでください。
  • SSR では生の ISO 文字列を描画し、useEffect で再整形する方法 — ハイドレーションに安全な時間表示の標準的なパターンです。再整形がレンダリング時に行われる場合に限り報告してください。
  • {} または no-store を返す headers エクスポート — 「キャッシュしない」という明示的な指定は意図的な判断であり、誤用として報告してはいけません。
  • 標準的なナビゲーションでの <Link prefetch="intent"> — 推奨されるデフォルトです。ローダーに副作用がある場合に限り報告してください。
  • headers エクスポートを介してドキュメントに転送される loaderHeaders — データとドキュメントのポリシーを同じ場所にまとめるのは文書化されたパターンであり、重複ではありません。

コンテキスト依存のルール

該当する具体的なコンテキストに限り、以下を適用してください。

問題次の場合に限り報告
headers エクスポートがないルートがキャッシュ可能な公開コンテンツを提供している(認証済みではない、パーソナライズされていない、意図的に no-store にしていない)
子ルートに headers がない祖先が headers をエクスポートしており、そのポリシーが子ルートのキャッシュ可能性より広範である(例:親が公開コンテンツを s-maxage でキャッシュし、子がパーソナライズされたデータを提供する)
Cache-Control: publicローダーが実際にセッション/ユーザー状態を読み取る(またはレスポンスに Set-Cookie が含まれる)
Vary: Cookie がないローダーが Cookie に応じてレスポンスの形式を分岐させ(テーマ、ロケール、セッション)、かつキャッシュが public/s-maxage である
new Date() / Math.random() / Date.now()呼び出し箇所がレンダリングパス内にある。useEffect、イベントハンドラー、<ClientOnly>、またはハイドレーション後のコード内ではない
ロケールを指定しないロケール整形結果が JSX に描画される(エフェクト/ハンドラー内のみで使用されるのではない)
<Link prefetch="render">リンクがリスト/.map() イテレーター内にある(ファーストビューの重要なナビゲーションではない)
副作用のあるローダーへの <Link prefetch="intent">ローダーに観測可能な副作用(分析データの書き込み、カウンターの増加、ログの出力)があり、かつ Purpose: prefetch ヘッダーで分岐していない
.server.ts なしでサーバーライブラリをインポートしているインポート元のファイルがクライアントの依存グラフから到達可能である(ルートモジュール、またはコンポーネントから到達可能な .server ではないユーティリティ)
process.env.SECRET_* の参照参照箇所がコンポーネント本体内、またはクライアントの依存グラフから到達可能な .server ではないモジュール内にある
画像に loading="lazy" がない画像がファーストビューより下に描画される(<header>、ヒーローセクション内、または <main> コンテンツより上ではない)
画像に width/height がないプロジェクトで、ビルド時に寸法を注入する画像プロセッサを使用していない

指摘を作成する前の必須ゲート

以下を順番に実施してください。これから報告するバッチのすべてのゲートに合格するまで、ユーザー向けの指摘を作成しないでください。

  1. 場所の証拠 — 合格条件: 各問題に、リポジトリ内のパスと、実際に読んだファイルの行範囲または短い逐語引用を記載します(記憶や差分だけに基づく推測は不可)。具体的なファイルパスがないキャッシュ、ハイドレーション、.server に関する主張は報告できません。

  2. 例外の確認 — 合格条件: 各問題について、有効なパターン(指摘しないこと)の対象外である理由を1行で説明できます。特に、headers エクスポートの欠落が意図的にキャッシュ不可のルートに該当しないこと、.client.ts が正当なクライアント専用ライブラリではないこと、suppressHydrationWarning がスコープ指定され、コメントで説明されていないことを確認してください。

  3. ハイドレーションコンテキストの確認 — 合格条件: new Date()、Math.random()、Date.now()、crypto.randomUUID()、またはロケールに応じた書式設定を指摘する前に、その呼び出し箇所がコンポーネントのレンダリングパスにあることを確認します。useEffect、useLayoutEffect、イベントハンドラー、setTimeout/requestAnimationFrame に渡されるコールバック内、または <ClientOnly>{() => ...}</ClientOnly> の内部の呼び出しはハイドレーション後であり、指摘してはいけません。

  4. 親子間のヘッダー継承チェーンの確認 — 合格条件: 「子に headers がないため、キャッシュ設定が暗黙に継承される」と指摘する前に、一致するルートチェーン内の祖先ルートが実際に headers をエクスポートしていること(ルートファイルツリーを検索して export const headers または export function headers を確認)と、継承されるポリシーが子ルートのキャッシュ可能性より広範であることを確認します。祖先が headers をエクスポートしていない場合、問題は「キャッシュが設定されていない」ことであり、「子が親のキャッシュを暗黙に継承している」ことではありません。

  5. サーバー/クライアント境界の確認 — 合格条件: サーバーライブラリのインポートを漏えいとして指摘する前に、インポート元のファイルがクライアントグラフから到達可能であることを確認します。つまり、ルートモジュール、ルートのデフォルトエクスポートから推移的にインポートされる非.serverユーティリティ、または.client.tsファイルであることが条件です。loader、action、headers、その他の.server.tsモジュール内にあるインポートは漏えいではありません。

  6. プロトコル — 合格条件: このレビューについて、review-verification-protocolの報告前検証チェックリストを完了しています。

リファレンスを読み込むタイミング

  • ルートのheadersエクスポート、Cache-Control文字列、CDNポリシーをレビューする場合 → references/caching-headers.md
  • .server.ts/.client.tsファイル、prisma/bcrypt/fsのインポート、またはprocess.envへのアクセスをレビューする場合 → references/server-client-split.md
  • 日付、ID、ロケールに応じて書式設定された値、またはブラウザーのグローバル変数をレンダリングするコンポーネントをレビューする場合 → references/hydration.md
  • <Link prefetch>、<PrefetchPageLinks>、defer、<Await>、<Suspense>、または<RemixServer abortDelay>をレビューする場合 → references/prefetch-streaming.md
  • <img>、<link>、dangerouslySetInnerHTML、フォント/CSSの読み込み、またはlinksエクスポートをレビューする場合 → references/assets.md

レビューの確認事項

  1. キャッシュ可能なデータを提供するすべてのルートで、「キャッシュなし」の場合も含め、Cache-Control ポリシーを宣言していますか?
  2. パーソナライズされたルートで public キャッシュを使用しておらず、セッションがレスポンスに影響する場合は Vary: Cookie を指定していますか?
  3. サーバー用ライブラリ(prisma、bcrypt、fs、シークレット環境変数へのアクセス)は、ビルド時に漏洩が拒否される *.server.ts モジュール内にありますか?
  4. 公開環境変数は process.env から展開するのではなく、ホワイトリストを使って window.ENV に設定していますか?
  5. new Date()/Math.random()/ロケール形式の呼び出しは、エフェクト、ハンドラー、または <ClientOnly> 内に限定され、レンダー時には使われていませんか?
  6. ID を必要とするコンポーネントでは useId() を使っていますか?
  7. <Link prefetch> のモードはコンテキストに合っていますか(ファーストビュー内では render のみ、ナビゲーションでは intent、リストでは viewport/intent)?
  8. defer() は実際に遅いデータにのみ使用し、<Await> には常に <Suspense> と errorElement の両方を組み合わせていますか?
  9. 画像にはサイズが指定され、ファーストビューより下では遅延読み込みされ、重要なフォント/CSS は links エクスポートを通じてプリロードされていますか?
  10. dangerouslySetInnerHTML は、サニタイズ済みの HTML、または安全にシリアライズされた JS にのみ使用していますか?

指摘を提出する前に

ハードゲート(特にゲート 3 — ハイドレーションコンテキストの確認、およびゲート 5 — サーバー/クライアント境界の確認)を完了してから、review-verification-protocol の報告前チェックを引き続き通過する問題のみを報告してください。指摘の形式:[FILE:LINE] ISSUE_TITLE に、問題のあるコードの引用(原文どおり)と、違反している特定の Remix v2 の仕様に結び付けた1行の根拠を添えてください。

追加ドキュメント

  • headers エクスポート、CDN キャッシュポリシー、Vary、親/子のマージ、Set-Cookie の相互作用のレビュー → references/caching-headers.md
  • .server.ts/.client.ts の境界、process.env へのアクセス、window.ENV のパターンのレビュー → references/server-client-split.md
  • レンダー時の Date/Math.random/ロケールに関する問題、useId、suppressHydrationWarning の適用範囲のレビュー → references/hydration.md
  • <Link prefetch> のモード、<PrefetchPageLinks> のターゲット、defer/<Await>/<Suspense> の構造のレビュー → references/prefetch-streaming.md
  • dangerouslySetInnerHTML、画像の loading/width/height、フォント/CSS のプリロード、スタイルシートの配置のレビュー → references/assets.md
  • 標準的なパターンと判断ゲート → remix-v2-perf-ssr

コメント