Crispy Profiling について
crispy-profilingは、CLI、GitHub Action、MCPサーバー、Agent Skillを通じてReactのレンダリングを分析します。コンポーネントの回数、不要な再レンダリング、その原因と修正案を示し、スナップショット比較で回帰を検出できます。
主な機能
- 01Chromiumでコンポーネントを分析
- 02不要な再レンダリングと原因を特定
- 03ソースコードの修正案を提示
- 04回数をスナップショット化して回帰確認
- 05CIとPlaywrightテストで実行
- 06MCPサーバーとAgent Skillを提供
活用シーン
- 01React操作の遅さの原因を特定
- 02プルリクエストのレンダリング変化を確認
- 03AIエージェントによるReact最適化を測定
- 04Playwrightのフローに描画チェックを追加
MCP 対応
MCPサーバーを通じてAIエージェントがReactの描画プロファイルを調べ、変更の評価に原因や修正案を利用できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
6掲載時のツール一覧 · 3 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| top | integer | いいえ | フェーズごとに表示するコンポーネント数 |
| url | string | はい | 分析するページ。例:http://localhost:5173/products |
| runs | integer | いいえ | |
| steps | array | いいえ | 読み込み後に実行する操作。操作中のレンダリングはフェーズ "interaction" に分類されます。 |
| outFile | string | いいえ | 完全な JSON レポートを書き込む任意のパス |
| timings | boolean | いいえ | フェーズごとの JavaScript 実行時間(ミリ秒)も測定します(レンダリング回数とは異なり、実行ごとに変動します)。インタラクションがどれだけ速くなったかをユーザーに伝えるために使用します。 |
詳細
🥓 crispy-profiling
React の再レンダリングのスナップショットテスト — 決定論的で、実行時に検証され、修正方法もわかります。
crispy-profiling は React の再レンダリングをプロファイリングするツールです(npm: crispy-profiling。CLI、GitHub Action、MCP サーバー、エージェントスキルを備えています。同名の crispy スタイルガイドパッケージや科学ソフトウェアとは関係ありません。
入力やクリックのときに React アプリが遅く感じますか?crispy は、理由なく再レンダリングされるコンポーネントと、修正すべき正確な行を示します。
クイックスタート
npm i -D crispy-profiling # or: pnpm add -D crispy-profiling · yarn add -D crispy-profiling
npx crispy install # downloads Chromium once (if that fails, an installed Chrome/Chromium is used)
npx crispy scan # zero config: starts your dev server, finds interactions, profiles them
npx crispy test # records crispy.snap.json from the scanned scenarios → commit it
pnpm または yarn を使っていますか?同じコマンドを pnpm exec crispy … または yarn crispy … として実行してください。
初めての方へ。知っておくべきことは3つです。
crispy scanはアプリを調べるだけです。コードを編集することも、どこかにデータを送信することもありません(データを変更するリクエストはブロックされます)。- 根本原因を短く一覧表示します。それぞれにファイル名と行番号が示され、変更内容が説明されます。「回避可能なレンダリングはありません」と表示された場合、試行したフローに問題はありません。
Not triedの行にはスキップした内容が表示され、動作が遅く感じる操作を追加する場所はcrispy.config.jsonです。 - 修正後は、もう一度
npx crispy testを実行します。🟢はレンダリングが減ったことを示します(画面が引き続き更新されることを確認してください)。🔴はレンダリングが再発したことを示します。指定しない限り(-u)、スナップショットが上書き保存されることはありません。
用語について:レンダーとは、React がコンポーネントを再度実行することです。回避可能とは、入力に実際の変更がなく、レンダリングしなくても画面の表示が同じであることを意味します。ホイストとは、定数をコンポーネントの外に移動することです。メモ化とは、レンダリング間で同じ値や関数を保持することです(useMemo、useCallback、React.memo)。同じ props でも、親コンポーネントがレンダリングされると、React.memo でラップされていない限りコンポーネントも再レンダリングされます。また、React.memo がレンダリングをスキップするのは props が安定している場合に限られるため、crispy は通常、その両方を求めます。
crispy scan は Next.js/Vite、開発用 URL(開発サーバーが出力するポート。例:vite.config.ts の server.port)、開発コマンドを検出し、いくつかのルートにアクセスして、安全な操作を試します(アイコンのみのものを含むボタン、タブ、セレクト、テキスト入力、内部リンク、ポインターカーソルが設定されたテーブルやリストの行。ただし、複数の言語で delete、pay、sign out、submit…などと名付けられたものは対象外です)。その後、修正方法とともに主な根本原因を表示します。crispy のすべてのコマンドと同様、読み取り専用です。GET 以外のリクエストや、ページが WebSocket 経由で送信するメッセージがブラウザーの外に出ることはありません(ホットリロード用のソケットを除く)。送信を試みた操作は報告され、保存されません(使い捨てデータのアプリでは --allow-writes を指定します)。本番環境ではなく、開発ビルドまたはプレビュービルドを指定してください。実行した内容は crispy.config.json として保存されるため、以降は crispy test でそのフローをチェックできます。.gitignore に .crispy/(レポート)を追加します。crispy.config.json と crispy.snap.json をコミットしてください。shadcn-admin では、設定を1行も書かずに2.5分で最初の根本原因を検出できました。手順を自分で書きたいですか?npx crispy init を実行すると、編集可能な設定ファイルが作成されます。
以降は npx crispy test を(ローカル、CI、または AI エージェントから)実行すると、コンポーネントが再レンダリングを始めたときに失敗し、その理由と修正方法が示されます。
| 🔴 regressed | list / interaction | Row | renders | — → 20 | recreated on every render (rendered at
src/App.tsx:222 (App)): `onSelect` is a new function with the same code in `App`: wrap it in
useCallback with the values it uses as dependencies. Then wrap this component in React.memo. |
フローを一度だけ確認するには、npx crispy run を実行すると、すべてのコンポーネントとその原因、修正方法が表示されます。
| Component | Renders | Avoidable | Callback | Why it rendered | Rendered at | Why / how to fix |
| Row | 20 | 0 | 20 | recreated callbacks 20 | `src/App.tsx:222 (App)` | `onSelect` is a new function with the same code in `App`: wrap it in useCallback… |
| Status | 1 | 1 | 0 | recreated props 1 | `src/App.tsx:178 (App)` | `style` is recreated with equal data in `App`: hoist it out of the component or wrap it in useMemo… |
| App | 1 | 0 | 0 | own state 1 | `src/main.tsx:12` | state updates here cause 23 avoidable render(s) below (`Row`, `Header`, `Status`)… |
各フェーズの冒頭には、まず対処すべき根本原因が表示されます。値を再生成する、コンテキスト値を再生成する、または状態を更新して変更のない子コンポーネントを再レンダーさせるコンポーネントのうち、回避可能なレンダーを引き起こす数が多い順に表示されます。また、カスケードの大部分を1つの React.memo で止められる子コンポーネントも示されます。
"Rendered at" と definedIn は、開発サーバーまたはバンドラー(Vite、webpack、Turbopack)が提供するソースマップを通じて、元のソースファイルと行に対応付けられます。ソースマップがない場合は、ブラウザーが実行するコードを指します。
Vite と Next.js(Turbopack および webpack の開発サーバー)で動作します。Next.js の開発オーバーレイなどのフレームワーク内部は除外されます。開発ビルドをプロファイルしてください。

ステータス:初期段階(0.x)で、毎週改善中です。 5 つのオープンソースアプリ(Redux Essentials、Next.js App Router Playground、Excalidraw、shadcn-admin、react-admin)で検証され、いずれでも修正可能な再レンダーの問題を検出しました。既知の制限事項と変更履歴をご覧ください。現時点で最も役立つ貢献は、バグ報告、誤ったヒントの報告、事例の共有です。
crispy-profiling は、ヘッドレス Chromium で React アプリを開き、指定した操作を実行して、どのコンポーネントが何回、なぜ(props/state/context/親)レンダーされたか、さらにどのレンダーが回避可能だったかを示します。コンポーネントごとのレンダー回数は再現可能です。そのため、同じシナリオのレポートは、コードが変更された場合にのみ異なります(コミット数とエフェクトのカスケードは読み込みのタイミングによって変わることがあり、スナップショットでは範囲として保存されます)。これにより、次の用途に信頼性の高いフィードバックループを提供します。
- AI コーディングエージェント:MCP サーバーと Agent Skill により、Claude Code、Cursor、Codex、Copilot などが推測ではなく、再レンダーの修正を測定できます。
- CI:レンダー予算とベースライン比較により、コンポーネントが再レンダーを開始すると PR を失敗させます。
- あなた自身:DevTools を開かずに「なぜ再レンダーされるのか?」を確認できる CLI です。
アプリのコードを変更する必要はありません。React DevTools と同じフックを使用します。React 19 でテスト済みで、React 18.3 および 19.0 のアプリで検証済みです。React 16.8~17 でも同じフックが公開されていますが、テストはしていません。
レンダースナップショット(crispy test)
Jest のスナップショットと同様ですが、対象は再レンダーです。主要なフローの期待レンダー回数をコミットしておけば、人間または AI エージェントが作成したすべての PR が、実行時にそれらと照合されます。
npx crispy test # 1st run: writes crispy.snap.json → commit it
npx crispy test # later: fails if any component renders more (or more avoidably)
npx crispy test -u # accept intended changes / lock in improvements
npx crispy test --ci # in CI: a missing snapshot fails instead of being written (auto-detected; --no-ci to opt out)
問題が再発した場合、コンポーネント、原因、レンダーされた場所、修正方法が表示されます(クイックスタートを参照)。
crispy.snap.json はコンポーネントごとに1行なので、PR の差分から、どの回数が変わったかを正確に確認できます。
"interaction": {
"commits": 1,
"components": {
"App": { "renders": 1, "avoidable": 0 },
"Header": { "renders": 1, "avoidable": 1 }
}
}
ルール:レンダー回数または回避可能なレンダー回数が増えると失敗します(snapshot.tolerance で許容範囲を設定できます)。各指標は個別にチェックされるため、改善によって回帰が隠れることはありません。コミット数の増加は ℹ️ として報告されますが、読み込みのタイミングによって変わるため失敗にはなりません(失敗させるには snapshot.failOnMoreCommits を指定します)。実行ごとに変動した回数は [min, max] の範囲で保存され、その範囲を超えた場合にのみ失敗します(-u を指定すると、既知の範囲を狭めずに保持します)。回数の減少は成功となり、-u の指定が推奨されます。新しい UI は成功として報告されます(-u で記録してください)。すでに回避可能なレンダーがある場合は ⚠️ として警告されます(代わりに snapshot.failOnNewAvoidable を設定すると失敗します)。レンダー回数は変わらず、回避可能なレンダーだけが増えた場合(修正によって次の原因が明らかになった直後によく見られます)、その行には「🟡 修正によって回避可能になりました」と表示され、失敗にはなりません(失敗させるには snapshot.failOnMoreAvoidable を指定します)。既知のコンポーネントが、あるフェーズで再レンダーを開始した場合は失敗します。同じ回数のまま名前を変更した場合は、別ファイルへの移動を伴っていても、回帰ではなく「🔁 名前変更」として報告されます。
crispy test がコミット済みファイルを独自に編集することはありません。スナップショットには常にすべてのコンポーネントが含まれ(topComponents を指定した場合も同様)、予算も引き続き適用されます。
Playwright テスト内で
すでに Playwright の E2E テストがある場合は、別のシナリオを作らず、その中で再レンダリングを検出できます。
import { test } from '@playwright/test';
import { renders } from 'crispy-profiling/playwright';
test('search does not re-render the product list', async ({ page }) => {
const r = await renders(page); // before page.goto
await page.goto('/');
await r.phase('search');
await page.fill('#search', 'shoes');
await r.toMatchSnapshot('search'); // __renders__/search.snap.json; fails with cause + fix
});
初回実行時にスナップショットが書き込まれるので、コミットしてください。意図した変更を受け入れるには CRISPY_UPDATE=1 を指定します。CI(CI が設定されている場合)では、スナップショットがないと失敗します。r.report() はカスタムアサーション用に完全なレポートを返します。
設定
crispy.config.json(JSON Schema):
{
"$schema": "./node_modules/crispy-profiling/schema/crispy.config.schema.json",
"baseUrl": "http://localhost:5173",
"runs": 3,
"scenarios": [
{
"name": "search",
"path": "/products",
"steps": [
{ "action": "type", "selector": "#search", "value": "shoes" },
{ "action": "phase", "name": "sort" },
{ "action": "click", "selector": "text=Price: low to high" }
],
"budgets": {
"interaction": { "maxAvoidableRenders": 0, "components": { "ProductCard": { "maxRenders": 20 } } }
}
}
]
}
| フィールド | デフォルト | 説明 |
|---|---|---|
baseUrl | — | 実行中のアプリのオリジンです。 |
runs | 3 | シナリオごとの実行回数です。レポートには中央値/最小値/最大値が保持され、回数が不安定な場合はフラグが付けられます。 |
settleMs | 300 | React のコミットも進行中のネットワークリクエストもない状態がこの時間続くと、ステップが「安定した」とみなされます。 |
maxSettleMs | 10000 | ステップごとの最大待機時間です。安定しないページ(ポーリング、アニメーションなど)では、処理が止まる代わりにレポートに警告が表示されます。 |
cpuThrottle | 1 | CPU の速度を落とします(例:4)。低速な CI ランナーやローエンドデバイスで回数を確認するのに使います。回数は変化しないはずです。 |
clock | false | フェイククロックでタイマー(setTimeout、setInterval、requestAnimationFrame、Date、performance)を制御し、ポーリングやアニメーションを行うアプリでも回数が決定的になるようにします。 |
timeoutMs | 30000 | ナビゲーション、ステップ、または安定化にかける最大時間です。 |
readOnly | true | プロファイリング中は GET 以外のすべてのリクエストと、ページが送信する WebSocket メッセージをブロックします(開発サーバー自身のホットリロード用ソケットのみ例外です)。これにより、クリックの再生によってデータが変更されることはありません。WebSocket 接続自体は引き続き確立され、受信もできます。破棄されるのはページが送信する内容のみです。ブロックされた書き込みはレポートの警告に記載されます。使い捨てのテストデータベースに対する場合など、書き込みが必要なフローに限って false に設定してください。 |
timings | false | フェーズごとのメインスレッド CPU 使用量(cost:JavaScript およびメインスレッド全体の作業時間をミリ秒で表示。プロダクションビルドでも計測)、コンポーネントの自己処理時間、LCP/CLS/長時間タスクを追加します。タイミングは再現性がないため、デフォルトでは無効です。 |
topComponents | 0 | フェーズごとに、レンダリング回数が最も多いコンポーネントのうち N 個だけを保持します(0 = すべて)。 |
viewport | 1280×800 | ブラウザーのビューポートです。 |
browser | headless | executablePath、channel(例:"chrome")、headless を指定します。CRISPY_CHROMIUM_PATH も使用できます。 |
includeInternals | false | フレームワーク/ライブラリ内部の要素を表示します(node_modules 内で定義され、ライブラリコードのみがレンダリングするコンポーネント。例:Next.js のルーター内部)。コードから直接レンダリングするライブラリコンポーネントは常に表示されます。 |
webServer | — | { "command": "npm run dev" }:crispy が開発サーバーを起動し、baseUrl(または url)に接続できるまで待機してから停止します。Ctrl-C を押した場合も停止します。ローカルでは、すでに起動しているサーバーを再利用します(警告が表示されます)が、CI では再利用せず、失敗します(上書きするには reuseExisting を指定します)。baseUrl から応答がなくてもサーバーが別のローカル URL を通知した場合、crispy はその URL を使い、そのことを知らせます。crispy init がこの設定を入力します。 |
login | — | { "path": "/login", "steps": [...] }:プロファイリングの前に一度だけサインインします(計測対象外)。環境変数からシークレットを読み込むには "${E2E_PASSWORD}" を使用します("$${NAME}" は ${NAME} をそのまま入力します)。 |
storageState | — | 保存済みセッションファイル(Cookie と localStorage)です。SSO/OAuth ログインの場合は、crispy login で作成したファイルなどを指定します。Git に含めないでください。 |
random | seeded | Math.random は実行ごとに同じシーケンスを返すため、フェイクデータ、ID、アニメーションが毎回同じようにレンダリングされます。native を指定すると、ブラウザーの動作が維持されます。 |
snapshot | crispy.snap.json, 0, false, false, false, false | crispy test で使う file(設定ファイルからの相対パス)、tolerance、failOnNewAvoidable、failOnMoreAvoidable、failOnMoreCommits、includeLibraries(アイコンなど、node_modules 内で定義されたコンポーネントも記録します。これらの回数はレンダリングするアプリのコンポーネントに追従するため、デフォルトでは無効です)。 |
compare | 10%, 1 | compare で使う rendersIncreasePct と minRendersDelta です。 |
ステップ: click、hover、fill、type、press、select(<select> の選択肢)、drag | ||
(selector から to へ、またはポインターの steps を指定して dx/dy でドラッグ)、scroll、waitFor(state: | ||
visible、hidden、attached、detached)、wait、goto、phase、expect(text または | ||
count を指定した selector):画面に期待どおりの内容が表示されない場合、シナリオを失敗にします。そのため、UI をフリーズさせる修正は、レンダー数が減って合格するのではなく、crispy test で失敗します。重要な結果が得られる各インタラクションの後に追加してください。例:{ "action": "expect", "selector": "#cart-count", "text": "3" }。 | ||
text と表示状態は表示中の一致要素を対象に確認されるため、非表示の重複要素(モバイルメニュー、スクリーンリーダー用ラベル)があってもステップは失敗しません。count はすべての一致要素を数えます(表示中の要素だけを数えるには "selector >> visible=true" を指定します)。ステップが失敗したシナリオはスキップされ、出力に名前が表示されます。他のシナリオでは引き続きレポートの生成またはスナップショットの比較が行われます。 | ||
type はキーを1つずつ押し、次のキーに進む前に React の処理が完了するのを待ちます(遅延値やトランジションを含みます)。そのため、同時実行機能のカウントは高速な CPU と低速な CPU で同じになります。最初のステップより前のレンダーはフェーズ load に記録され、ステップ中のレンダーは、{ "action": "phase", "name": "..." } でフェーズ名を指定しない限り、interaction に記録されます。 |
予算(フェーズごと):maxCommits、maxTotalRenders、maxAvoidableRenders、maxWastedRenders、およびコンポーネントごとの maxRenders/maxAvoidableRenders/maxWastedRenders。
シナリオで一度も発生しないフェーズの予算は設定エラーです。レンダーされたコンポーネントに一度も一致しないコンポーネント予算は警告を生成します(入力ミスの可能性があります)。topComponents によってレポートの表示が絞り込まれていても、予算の判定では常にすべてのコンポーネントが対象になります。
数値の意味
| フィールド | 意味 |
|---|---|
renders | コンポーネント関数/クラスがレンダリングされた回数です(マウント+更新)。 |
avoidableRenders | 実質的に何も変わっていない更新です。無駄なレンダリングに加え、再作成されたものの等価なデータ(オブジェクト、配列、要素、日付、マップなど)だけが原因のレンダリングも含みます。確実に回避できます。 |
callbackRenders | 同じコードの関数が再作成されたこと(インラインコールバック)だけが原因の更新です。キャプチャする値が変化していない場合は回避できます。crispy はキャプチャを確認できないため、別に報告されます。 |
wastedRenders | props(浅い比較)、state、消費された context のいずれも変化していない更新です。 |
causes.props / state / context | その入力が変化した更新です(1 回の更新に複数の原因がある場合があります)。 |
causes.unstable | データの同一性だけが変化しました。オブジェクト/配列リテラル、日付、マップ、context の値、または等価な内容で再作成されたフックの結果です。 |
causes.callback | 同じコードの関数だけが再作成されました(バインド関数/ネイティブ関数は実際の変更としてカウントされます)。 |
causes.parent | 入力に変更がない更新です。親が再レンダリングされました(wasted と同じです)。 |
unstableProps | 等価なデータで再作成された prop キーです。useMemo を使うか、定数をコンポーネントの外に定義して修正します。 |
callbackProps | コールバックとして再作成された prop キーです。適切な依存関係を指定して useCallback を使うか、React Compiler で修正します。 |
changedProps | 同一性が変化した prop キーとその回数です。causes.props の「理由」を示します。 |
triggeredBy | このコンポーネントを再レンダリングする連鎖を開始した、自身の state 更新が行われたコンポーネントとその回数です。すべての子ではなく、トリガーを修正してください。 |
recreatedContextFrom | 等価な内容で value が再作成された context provider を持つコンポーネントです(例:value={{ user, logout }})。その箇所で値をメモ化してください。 |
memo | コンポーネントが React.memo でラップされている場合は true です。そのため、ヒントが再度ラップするよう提案することはありません。 |
memoSkips / uselessMemo | シナリオ内の全フェーズで React.memo がスキップしたレンダリングです。3 回以上更新され、一度もスキップされなかったメモ化コンポーネントには uselessMemo フラグが付き、削除を検討するようヒントが表示されます。これは、あらゆるものをメモ化することを防ぐためのガードレールです。 |
stateChanges | コンポーネント自身の state が原因でレンダリングされた場合に、どの state が変化したかを示します。フックがコンポーネント内に記述されている場合は `query` (useState)、ストアおよびルーターのフック(Redux、Zustand、ルーター)の場合は store subscription (useSyncExternalStore) in useAppSelector`` と表示されます。複数のカスタムフックが該当する可能性がある場合は「in one of …」と表示されます。React DevTools にはフックの番号しか表示されません。 |
effectCascades | レンダリング直後に、このコンポーネントの useEffect が設定する state です。自身の state(`d` (useState))、setter prop 経由の親の state(… in `Parent` (via a prop))、またはストアの state(a store read by `Reader`)が含まれます。それぞれが追加のコミットです(cascadeCommits、その中で cascadeRenders 回レンダリング)。ヒントには削除方法(レンダリング中に計算する、イベントハンドラーで設定する、またはそこで親の setter を呼び出す)が表示されます。対象外:レイアウトエフェクト(DOM の計測)、トランジション、遅延値、コンポーネントのマウントが原因で実行されたエフェクト。React 18 以降では createRoot が必要です。 |
creators | `prop |
staleMemo | `prop |
providerAt | 再作成された context の値を持つ provider がレンダリングされる場所です。 |
compiled | React Compiler がコンポーネントをコンパイルした場合は true です。 |
locations | コンポーネントがレンダリングされる最大 3 か所を file:line (Owner) の形式で示します(Owner の JSX 呼び出し箇所、頻度の高い順)。利用可能な場合はソースマップを使って解決されます。 |
Item (src/List.tsx) keys | コンポーネントは名前と定義ファイル(DevTools プロトコル経由で解決)で識別されます。同じ名前の別々のコンポーネントには Name (file) 形式のキーが付くため、無関係な Item を追加しても既存の名前は変わりません。スナップショットにはファイルが保存され、一致するものが維持されます。ファイルで区別できない場合(styled-components、HOC ファクトリー、1 つのファイル内の複数のコンポーネント)は、レンダリングされる場所(Item @ src/Card.tsx:12)がキーになります。番号付きキー(Item#2)は最後の手段です。キーは goto によるナビゲーションをまたいで維持されます。 |
definedIn | コンポーネント関数が定義されているファイルです。 |
stable | 実行間で回数が異なる場合は false です(タイマー、ネットワーク、ランダム性)。 |
すべての Markdown レポート(crispy run、crispy test、MCP ツール)には、これらのフィールドをもとにした 理由/修正方法 列が追加されます。ヒントは根本原因を示します。連鎖反応の起点となるコンポーネント、プロパティを再生成するオーナー(file:line 付き)、またはコンテキスト値を再生成するプロバイダーです。 |
開発用ビルドをプロファイルしてください。本番用ビルドではコンポーネント名が最小化されます。
CLI
crispy scan [url] [--routes 3] [--actions 5] [--allow-writes] Zero config: find, profile and save interactions
crispy init [--base-url <url>] Create crispy.config.json (detects framework, URL, dev command)
crispy install [--with-deps] [--verbose] Download the Chromium build crispy uses (--verbose: full download log)
crispy login [-c file] [--path /login] Sign in by hand in a browser window and save the session
crispy run [-c file] [-o file] [-s scenario...] [--markdown file] [--no-fail]
crispy test [-c file] [-u|--update] [--ci] [-s scenario...] [--markdown file]
crispy compare <base.json> <head.json> [--threshold 10] [--min-delta 1] [--markdown file] [--json file] [--no-fail]
crispy mcp Start the MCP server on stdio
終了コード:0 正常 · 1 予算違反、リグレッション、または expect ステップの失敗 · 2 使用方法/実行時エラー
AI エージェント向け
MCP サーバー
ツール:scan_app(設定がまだなくても、インタラクションを検出して主な原因を返します)、profile_url、
run_scenarios、test_render_snapshots、compare_reports、inspect_component
{
"mcpServers": {
"crispy-profiling": { "command": "npx", "args": ["-y", "crispy-profiling@latest", "mcp"] }
}
}
Claude Code:claude mcp add crispy-profiling -- npx -y crispy-profiling@latest mcp
Claude Code プラグイン(MCP サーバー+スキル)
/plugin marketplace add edgeorgie/crispy-profiling
/plugin install crispy-profiling@crispy-profiling
エージェントスキル(Claude Code、Cursor、Codex、Copilot、Gemini CLI、…)
npx skills add edgeorgie/crispy-profiling
このスキルでは、計測 → 修正 → 再計測 → 比較のループと、各シグナルを修正方法(React.memo、useCallback、useMemo、コンテキストの分割、状態のコロケーション)に結び付ける方法をエージェントに教えます。
CI(GitHub Action)
.github/workflows/crispy.yml として保存します。
name: crispy
on: pull_request
permissions:
contents: read
pull-requests: write # lets crispy comment on the PR
jobs:
renders:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: 22 }
- run: npm ci
- uses: edgeorgie/crispy-profiling@v0 # starts your dev server (webServer) and runs `crispy test --ci`
このアクションは Chromium とそのシステムライブラリを自動でインストールするため、ワークフローでブラウザーを設定する必要はありません。
最初の実行前に、ベースラインを記録してコミットしてください。ローカルで npx crispy test を実行し、crispy.snap.json をコミットします。これを行わないと、ジョブは「missing snapshot」で失敗します(仕様です。CI が代わりに記録することはありません)。@v0 は 0.x リリースを指す可変タグです。固定するには @v0.2.0(またはコミット SHA)を指定してください。フォークからのプルリクエストでは、GitHub がトークンに書き込み権限を付与しないため、警告とともに PR コメントがスキップされます。ジョブのサマリーと合否結果は引き続き表示されます。
ジョブが失敗した場合は、ジョブのサマリーを開き、示されている原因を修正して、もう一度プッシュしてください。変更が意図したものであれば、担当者が npx crispy test -u を実行し、新しいスナップショットをコミットします。
各シナリオは 3 回実行されるため(runs)、シナリオが多い設定では数分かかることがあります。matrix と scenarios 入力(名前はスペースまたはカンマで区切ります)を使って複数のランナーに分割すると、各ジョブはコミット済みスナップショットの担当分だけをチェックします。
strategy:
matrix:
scenarios: ['tasks users', 'settings sidebar-toggle']
steps:
# checkout, setup-node, npm ci as above
- uses: edgeorgie/crispy-profiling@v0
with: { scenarios: '${{ matrix.scenarios }}' }
ローカルで同じ分割を行うには、npx crispy test -s tasks -s users を実行します。GitHub ランナーでは、Chromium のインストールに約 1 分かかるほか、シナリオごとに実行 1 回あたり約 20~25 秒かかります。
いずれかのコンポーネントのレンダリング数がコミット済みスナップショットの許容数を超えた場合(または予算を超過した場合)、ステップは失敗します。ジョブのサマリーと、プッシュのたびに更新される PR コメント 1 件に、各リグレッションの原因、レンダリング箇所、推奨される修正方法が表示されます(無効にするには comment: false を指定します)。予算のみのチェックやベースラインとの比較を行う設定では、command: run(任意の baseline レポート付き)も利用できます。ワークフロー全体については、examples/github-workflow.yml を参照してください。
プログラムから利用する API
import { compareReports, parseConfig, profile } from 'crispy-profiling';
const config = parseConfig({ baseUrl: 'http://localhost:5173', scenarios: [{ name: 'home' }] });
const report = await profile(config);
比較
各ツールは異なる問いに答えるため、組み合わせて使ってください。
| ツール | 用途 | crispy の役割 |
|---|---|---|
| React DevTools Profiler | ブラウザーでの対話的な手動プロファイリング | crispy は同様の分析をヘッドレスで、スクリプト化された操作に対して毎回実行します |
| React Scan | アプリの使用中に再レンダーを視覚的にハイライトします。プログラムから利用できる onRender API もあります | crispy はレンダー数をコミット済みのスナップショットにし、CI で失敗させられるようにします。コンポーネントごとに修正のヒントも示します |
| why-did-you-render | 開発中に、避けられる再レンダーについてコンソールに通知します(Babel のセットアップが必要です) | crispy はアプリの変更が不要で、操作ごとに決定論的な結果を報告します |
| React Doctor | コードベースを静的解析(lint ルール)し、スコアを表示します | crispy は実行時に実際にレンダーされたものを観察します。静的解析の指摘と実行時の証拠は相互に補完します |
| React Compiler | ビルド時に自動でメモ化します | crispy はコンパイラーでカバーできなかったもの(たとえば、レンダーのたびに変わる依存関係)を示し、結果を検証します |
crispy が追加するのは、決定論的なカウント(同じコードなら同じレポート)、CI でのスナップショット、 根本原因のヒント(不安定な値を作る箇所、変化する依存関係)、そして検証 (修正が 🟢 改善として表示されます)です。
既知の制限事項
- 🟢 が示すのはレンダー数の減少であり、UI が正常に動作することではありません。 可変データ(テーブルやフォームのインスタンス)を読み取るコンポーネントに React.memo を適用すると、レンダーと更新の両方がなくなることがあります。修正後は、該当する画面が必要なときに引き続き変化することを確認してください。
- 開発ビルドのみ対応しています。 本番ビルドでは、crispy が原因や位置の特定に使うコンポーネント名とデバッグ情報が削除されます。
- レンダー数はミリ秒ではありません。 crispy は避けられるレンダーを決定論的に見つけますが、それが重要かどうかはコンポーネントの処理コストによります。各フェーズの CPU コスト(ミリ秒)に加えて、self time、LCP、long task を確認するには
timings: trueを使ってください(再現性はありません)。 - 計測にはコストがかかります。 レンダーフックによりメインスレッドの処理が増えます(shadcn-admin では JavaScript の実行時間が約 25 % 増加)。crispy を使った実行同士でミリ秒を比較し、計測していないブラウザーとは比較しないでください。
- Web のみ、Chromium のみ対応しています。 React Native には対応していません。レンダー数の計測にほかのブラウザーは不要です。
- シナリオは手動で作成するか、
crispy scanで生成します(安全な操作のみ)。 - ヒントはヒューリスティックです。 実際のアプリで検証したところ、ほとんどのヒントは適切なコンポーネントを示していましたが、すべてがそのまま実行可能な対策につながるわけではありませんでした。誤ったヒントを報告する際は、レポートを添付してください。こちらで修正します。
- 実際のネットワークタイミングに左右されるアプリでは、実行ごとに結果が変わることがあります。カウントは範囲として保存され、乱数のシードを固定し、追加のコミットを 1 回まで許容しますが、タイミングに大きく左右されるフローでは
waitForのステップやclock: trueが必要になる場合があります。
これをどのように構築しているか
crispy-profiling は、人間の指示のもと、AI コーディングエージェント(Claude Code)を使って開発されています。アトミックなコミット、テスト、Node 20/22/24 での CI、レビューといったルールは、ほかのすべての貢献と同じです。各マイルストーンは、独立したエージェントが厳しいレビュアーとして確認し、実際のオープンソースアプリで検証しています。この README と changelog に記載された数値はすべて、掲載されているコマンドで再現できます。問題を見つけた場合は、issue を作成してください。
仕組み
初期化スクリプトは、React の読み込み前に __REACT_DEVTOOLS_GLOBAL_HOOK__ をインストール(またはラップ)します。コミットのたびに、React DevTools と同様に新しい fiber ツリーをその alternate と照合し、処理を実行した各コンポーネントを記録します。また、props、フックの状態、コンテキストの値を比較して、原因を分類します。docs/ARCHITECTURE.md を参照してください。
コントリビューション
ご利用のアプリで試しましたか? アプリとバージョン、コマンド、crispy の出力、期待していた内容、変更した内容、カウントに起きたことを、5 行で十分です。"自分のアプリで試しました"の issue を作成してください。誤ったヒントについては専用フォームがあります。
PR も歓迎します。CONTRIBUTING.mdをご覧ください(GitFlow:develop からブランチを作成してください)。AI コーディングエージェントは、まず AGENTS.md をご覧ください。LLM 向けのドキュメントインデックス:
llms.txt。
ライセンス
MIT © Edwin Jorge
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。