| name | react-performance |
| description | Use this skill when you need to diagnose and fix React 19 performance issues (render churn, slow updates, Suspense/Transitions, memoization, profiling, and React Compiler). |
| metadata | {"applyTo":"**/*.tsx, **/*.ts, **/*.jsx, **/*.js"} |
React Performance Agent Skill (React 19, 2025)
This skill is for measuring, diagnosing, and fixing performance problems in React code. It is intentionally different from react-expert:
- If the task is primarily about eliminating unnecessary
useEffect and choosing correct React patterns, use: .github/skills/react-expert/SKILL.md
- If the task is about runtime cost, re-render churn, slow inputs, Suspense waterfalls, or memoization strategy, use this skill.
What “performance” means in React
React performance problems usually come from one (or more) of these:
- Too much rendering (a component renders often, or too much work happens per render).
- Updates that block user input (urgent + non-urgent work mixed together).
- Async UI that reveals/hides too often (Suspense boundaries at the wrong level).
- Unstable identities causing memoization to fail (new objects/functions/arrays every render).
- Large client bundles / hydration work (especially in Server Components apps).
Non‑negotiables (correctness first)
Performance fixes must not break React’s rules.
- Render must be pure; no side effects during render.
- Keep components pure (helps both correctness and predictability under concurrent rendering).
- Don’t “lie” to dependency arrays. If deps feel wrong, change the code.
A practical workflow (do this in order)
1) Reproduce and name the symptom
Capture what “bad” means:
- “Typing lags in search input.”
- “Navigating tabs causes 1–2s freeze.”
- “List scroll stutters.”
- “Clicking button feels delayed (spinner appears late).”
If you can’t reproduce reliably, stop and add a reproduction harness (storybook page, dev route, a small test page).
2) Measure: use the Profiler
Use React DevTools’ Profiler for interactive diagnosis, or use <Profiler> for programmatic measurements.
If you need a deeper, timeline-style view (Scheduler priorities, render/commit/effects, cascading updates), use React Performance tracks in browser Performance tooling:
- React Performance tracks (profiling builds, Scheduler/Components/Server tracks):
Key interpretation:
- High
actualDuration on updates suggests expensive renders.
- Large gap between
baseDuration and actualDuration indicates memoization is helping (or could help more).
3) Identify the kind of waste
Ask these questions:
- Is the component re-rendering when its meaningful inputs didn’t change?
- Is the component doing expensive work during render?
- Is a parent re-render forcing a large subtree to re-render?
- Are you causing extra renders via state “ping-pong” (e.g., setState loops)?
4) Fix in the lowest-risk order
Prefer these changes, roughly in this order:
- Reduce the amount of state (derive values; keep state minimal).
- Reduce subtree size in Client Components (push logic up to Server Components where possible).
- Split components to isolate re-render boundaries.
- Use concurrency primitives (
useTransition, useDeferredValue) to keep urgent updates responsive.
- Use memoization (
memo, useMemo, useCallback) where it actually helps.
- Consider React Compiler to automate memoization (where compatible).
Memoization: when it helps (and when it doesn’t)
Memoization is an optimization tool; don’t reach for it blindly.
memo (component memoization)
Use memo when:
- The component is expensive to render and
- It often receives the same props and
- Props are referentially stable (or you can make them stable)
Docs:
useMemo (memoize expensive computations)
Use useMemo when you can point to:
- A computation that is meaningfully expensive, and
- It runs frequently, and
- Its inputs change less frequently
Docs:
useCallback (stable function identity)
Use useCallback when:
- You pass a callback to memoized children, and
- Changing the callback identity causes avoidable re-renders
Docs:
Avoid “identity churn” first
Before sprinkling useCallback everywhere, check for common sources of churn:
- Inline object literals:
{ foo: bar }
- Inline arrays:
[a, b]
- Inline callbacks:
() => doThing(id)
If these are passed as props, memoization may never kick in.
React Compiler (2025-friendly default)
If the environment supports it, React Compiler can eliminate a lot of manual memoization work.
If you need fine-grained control (rare), the compiler also supports directives as escape hatches:
- Directives (
"use memo", "use no memo") and best practices:
Guidance:
- Don’t prematurely add
useMemo/useCallback everywhere “for perf”.
- Prefer code that’s easy for the compiler to optimize: pure render logic, stable patterns, minimal mutation.
- When performance issues exist, still profile: the compiler doesn’t replace measurement.
If you adopt the compiler incrementally, document which areas are compiled and which aren’t.
Keep urgent interactions responsive
useTransition / startTransition
Use transitions to mark updates as non-urgent (e.g., filtering/sorting results while typing).
Docs:
Notes:
- Use it to keep inputs responsive while rendering expensive results.
- Expose pending state in the UI for non-urgent updates.
useDeferredValue
Use deferred values when:
- You want to render “old” results while a new value is propagating through an expensive subtree.
Docs:
Suspense: reduce waterfalls and avoid “UI hiding”
Suspense is powerful, but boundary placement is everything.
Docs:
Heuristics:
- Put Suspense boundaries around the slow part, not the whole page.
- Prefer showing stale content while new content loads, using transitions/deferred values.
- Avoid nested Suspense causing sequential waterfalls unless it’s intentional.
Forms & Actions: performance is UX
Many “slow app” complaints are latency/feedback issues. Use modern React 19 form/action patterns to keep the UI feeling instant.
References:
<form action={fn}> supports functions (Actions) and resets uncontrolled fields after success:
useActionState can show server responses and pending state (also enables progressive enhancement):
- Server Functions / Server Actions (React Server Components):
For deep guidance on choosing Server Actions vs client code in this repo, see:
.github/skills/nextjs-server-actions/SKILL.md
Lists and large UI trees
React can only optimize what it can avoid rendering.
Checklist:
- Use stable, meaningful
keys (avoid index keys for dynamic lists).
- Split list rows into memoized leaf components only if profiling shows benefit.
- Consider windowing/virtualization for very large lists (outside React core; pick a library).
Code review checklist (performance)
When reviewing a PR for performance risk, answer:
Cost & performance notes (Freelancerino)
This is a Next.js 16 server-first app:
- Keep
'use client' components leaf-ish to reduce bundle + hydration work.
- Prefer server-side data shaping and pass serializable props to the client.
For Vercel/runtime-specific guidance, see:
.github/skills/nextjs-cost-performance/SKILL.md
References (official)
References (repo)
- React patterns / eliminating
useEffect: .github/skills/react-expert/SKILL.md
- Next.js server actions conventions:
.github/skills/nextjs-server-actions/SKILL.md
- Next.js runtime cost/perf:
.github/skills/nextjs-cost-performance/SKILL.md