React DevTools Profiler Export Analysis

SkillMedia

Lets your agent analyze a React DevTools Profiler export to explain why components re-rendered and what they cost.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the React DevTools Profiler Export Analysis skill

About this capability

Use when analyzing a React DevTools Profiler export (.json with "dataForRoots"/"commitData") — re-render storms, commit fan-out, "why did this component render", send/interaction render cost. NOT for Chrome Performance traces (those have "traceEvents" — use electron-profile).

What this skill tells your AI

The instructions your AI receives, as published by keybase/client in skill/react-devtools-profile/SKILL.md and read by ahel’s review.

Parse React DevTools Profiler exports (version 4/5 JSON, top-level dataForRoots) with Python scripts in scripts/ — no DevTools UI needed. Works for React Native and web/Electron profiles alike.

Distinguish from a Chrome Performance trace: profiler export has {"version": N, "dataForRoots": [...]}; a trace has traceEvents (use the electron-profile skill for those).

Workflow

  1. Overview — commit totals, top commits with updaters, component ranking by aggregate self duration, timeline buckets:
    python3 scripts/profile-overview.py <profile.json> [--top 25] [--bucket-ms 500]
    
  2. Per-commit detail — full updater list, fiber counts, top fibers with ancestor paths, changeDescriptions if recorded:
    python3 scripts/profile-commits.py <profile.json> [--commit N] [--top 10] [--root 0]
    

Run from the skill directory (or use absolute script paths); both scripts need scripts/rdtlib.py alongside. The overview's top-commit list prints #N — that is the index to pass as --commit N (commits are chronological).

Interpreting results

SignalMeaning
updaters on a commitFibers that scheduled the update — setState / store subscription (useSyncExternalStore) firing. The render entry point, not necessarily where time went.
Same broad updater set repeated across several commitsShared-store fan-out: many components' selectors woke for one logical change. Find the common denominator subscription (here it was conversation meta) and fix identity churn there.
Unnamed fibers #1234Fiber absent from the end-of-profiling snapshot = unmounted before profiling stopped (virtualized-list churn, pending rows replaced on server ack). Cross-reference the same id in updaters, which carries displayName.
prio=ImmediateSync flush — store setState outside a React event (zustand, RPC callbacks). Each one blocks the frame; many small Immediate commits = stacked sync flushes worth batching.
High x<count> in component rankingComponent rendered in many commits — subscription too broad or unstable selector output (fresh object/closure identity per run defeats useShallow).
Large single-fiber self time in a big-fiber-count commitUsually a subtree mount (new list row). Mount cost is mostly inherent; look at whether it mounts twice (key churn).
changeDescriptions: nullProfile recorded without "Record why each component rendered". Re-record with it enabled to get props/state/hooks/context attribution per fiber.

Scope caveat: durations are React render/effect time only. Store selector runs, native layout, bridge, and app JS outside render are invisible here — a small total does NOT mean no perf problem; it means the problem isn't React commits.

Fan-out diagnosis recipe: overview → spot repeated updater sets → per-commit to list exact updater components → read their store selectors → find the shared subscription whose selected values change identity without changing content → preserve identity at the producer (deep-equal reuse on store write), not with deeper memo at consumers.

Gotchas

  • Timestamps are ms relative to profiling start; scripts print t= relative to first commit.
  • fiberActualDurations = inclusive per fiber; fiberSelfDurations = self. Ranking uses self.
  • Multiple roots possible (dataForRoots); overview iterates all, per-commit takes --root.
  • Updater lists carry displayNames even for fibers missing from the snapshot — best way to name unmounted fibers.

Signals

GitHub stars
9k
Forks
1k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
react-devtools-profile
Source
github.com/keybase/client