A few weeks ago I published a post about react-render-profile-mcp — an MCP server that decodes React DevTools Profiler exports so AI agents can actually diagnose render performance instead of guessing at raw fiber IDs.
v0.1 shipped with 5 tools. v0.3.1 adds 4 more, each targeting a class of problems the original couldn't touch. Here's what's new. 🐸
What was already there (v0.1)
Quick recap for anyone who missed the first post:
-
get_render_summary— total commits, render time, top components, lifecycle anomaly flags -
find_spurious_renders— components that re-rendered with no actual prop/state change -
get_hottest_components— ranked by CPU self-time -
trace_render_cascade— what triggered a commit and what re-rendered as a result -
suggest_memoization— ROI-scoredReact.memorecommendations
These covered the "something is rendering too much" class of problems. What they couldn't tell you: why your React Compiler isn't helping, where hydration broke, which Zustand selector is in a loop, or how deep a context update actually propagates.
What's new in v0.3.1
1. analyze_compiler_efficacy
React Compiler (React 19) and manual React.memo should eliminate spurious renders. Often they don't — because of inline object allocations or unstable parent refs that bypass memoization entirely.
This tool computes an Invalidation Index per component:
I = (spurious_count / total_count) × wasted_ms
High index = memoization is present but not working. The tool tells you exactly why:
{
"severity": "CRITICAL",
"component_name": "ProductList",
"ineffective_render_count": 23,
"wasted_ms": 84.3,
"trigger_cause": "UNSTABLE_PARENT_PROP_REFERENCE",
"recommendation": "Memoize parent props with useMemo/useCallback or hoist static objects out of the parent render function."
}
Without this, your agent might suggest adding React.memo to a component that already has it — and has it for a reason that makes it useless.
2. diagnose_hydration_and_suspense
Two separate problems, one tool:
Hydration mismatches — when React discards server HTML and remounts the entire tree from scratch. Shows up as an abnormally long initial mount with a spike of unmounts immediately after. The tool flags these as HYDRATION_MISMATCH_RECOVERY with the affected Suspense boundaries and blocking duration.
Suspense waterfalls — nested boundaries fetching sequentially instead of in parallel. Detected by measuring the gap between consecutive Suspense resolves against a configurable waterfall_threshold_ms (default: 100ms).
{
"severity": "WARNING",
"anomaly_type": "NESTED_MOUNT_FETCH_WATERFALL",
"root_component": "ProfileDetails",
"blocking_duration_ms": 120.5,
"recommendation": "Prefetch data at the parent level or use Promise.all."
}
3. evaluate_external_store_performance
Zustand and Redux with useSyncExternalStore have two failure modes that are hard to catch manually:
Unstable selector object allocation — a selector returns a new object reference every call, triggering rapid consecutive renders. The tool detects components that render multiple times in consecutive frames and flags the selector as the cause.
Sync concurrency bypass — a heavy store update runs synchronously in a high-priority lane, blocking the main thread. Should be in startTransition instead.
{
"severity": "CRITICAL",
"impacted_components": ["CartSummary"],
"is_infinite_loop": true,
"trigger_cause": "UNSTABLE_SELECTOR_OBJECT_ALLOCATION",
"recommendation": "Wrap the selector in useCallback or return primitive values."
}
4. trace_state_cascade_footprint
Given a commit index, this reconstructs the virtual component tree and measures how far an update actually propagated:
- Which component triggered the update
- Whether it went through a context provider or a store subscriber
- How many levels deep it reached
- How many consumer components re-rendered
{
"severity": "HIGH_FOOTPRINT",
"update_trigger_source": "ThemeButton",
"propagation_channel": "CONTEXT_PROVIDER",
"cascade_render_depth": 7,
"rendered_consumer_count": 28,
"recommendation": "Split the context provider or memoize its value and children."
}
This is the tool that answers "why did 28 components re-render when I clicked one button."
Updated: find_spurious_renders trigger classification
The existing tool now classifies why a render was spurious, not just that it was:
-
UNSTABLE_PARENT_REF— parent passed a new object/array/function reference with identical values -
CONTEXT_UPDATE— context changed, but this component doesn't actually use the changed value -
INTENTIONAL_CONCURRENT_YIELD— React's scheduler, not a bug
This matters because React.memo fixes the first case but can't help with the second. The recommendation now reflects the correct fix path.
Recommended agent workflow (updated)
1. get_render_summary → overview + lifecycle_anomaly flags
2. find_spurious_renders → classify unnecessary renders by trigger type
3. analyze_compiler_efficacy → check where React.memo / Compiler is being bypassed
4. diagnose_hydration_and_suspense → catch hydration recovery + Suspense waterfalls
5. evaluate_external_store_performance → Zustand/Redux selector loops + sync bypasses
6. trace_state_cascade_footprint → propagation depth for expensive commits
7. suggest_memoization → ROI-scored final recommendations
Setup (unchanged)
{
"mcpServers": {
"react-render-profile": {
"command": "npx",
"args": ["-y", "react-render-profile-mcp"]
}
}
}
Export a profile from React DevTools → Profiler tab → Record → Save. Pass the .json path as profile_path to any tool.
npm: react-render-profile-mcp
GitHub: vola-trebla/react-render-profile-mcp
Happy to answer questions about the hydration detection heuristics or the Invalidation Index math. And if you're hitting a React performance pattern this doesn't cover yet — open an issue. 🐸
Top comments (0)