React performance
Skill jgamaraalv/delivery-loop/.claude/skills/react-performance
React performance: unnecessary re-renders, memoization (React.memo/useMemo/useCallback), React Compiler, Context, concurrent features, code splitting, and Core Web Vitals.From its SKILL.md
npx -y skills add jgamaraalv/delivery-loop --skill react-performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.0 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it
Performance Debugging Workflow
Always follow this order — measure before optimizing:
- Reproduce — Document exact steps, browser, device, network conditions
- Measure — Profile with React DevTools Profiler + Chrome Performance tab. Never measure development builds.
- Identify — Form a specific hypothesis about the cause
- Fix — Apply the minimal fix needed
- Verify — Measure improvement under same conditions
Rule #1: Do NOT measure the development build. For production profiling, alias:
react-dom$→react-dom/profilingscheduler/tracing→scheduler/tracing-profiling
Rule #2: Simulate real user conditions (slow CPU, throttled network).
Why Components Re-render
Components re-render for exactly three reasons:
- Its state changed
- Its parent rendered (unless wrapped in
React.memo) - A consumed Context value changed
Renders are either necessary or unnecessary. Any single unnecessary render is rarely a problem — it's the accumulation over time.
State Architecture (Fix Before Memoizing)
Before reaching for memoization, fix your state architecture. These are free performance wins:
Colocate State
Move state to the closest component that needs it. State at the root re-renders the entire tree.
// BAD: searchQuery in App re-renders Header, Cart, Footer
function App() {
const [searchQuery, setSearchQuery] = useState("");
return (
<>
<Header />
<SearchBar query={searchQuery} onChange={setSearchQuery} />
<Cart />
</>
);
}
// GOOD: searchQuery lives in SearchSection
function App() {
return (
<>
<Header />
<SearchSection />
<Cart />
</>
);
}
function SearchSection() {
const [searchQuery, setSearchQuery] = useState("");
return <SearchBar query={searchQuery} onChange={setSearchQuery} />;
}
Derive, Don't Store
If a value can be computed from existing state/props, compute it — don't store it.
// BAD: synchronized state
const [items, setItems] = useState<Item[]>([]);
const [filteredItems, setFilteredItems] = useState<Item[]>([]);
// Must keep in sync — bugs + extra renders
// GOOD: derived value
const [items, setItems] = useState<Item[]>([]);
const filteredItems = items.filter((i) => i.active); // or useMemo if expensive
Lift State Intelligently
Only lift state to the lowest common ancestor that needs it — no higher.
Memoization Decision Guide
Three memoization tools, each with specific use cases:
| Tool | What it caches | Use when |
|---|---|---|
React.memo | Component render output | Profiler shows component re-renders due to parent, not its own props/state |
useMemo | Computation result | Expensive calculation runs on every render, OR referential stability for memoized children |
useCallback | Function identity | Function passed as prop to React.memo-wrapped child, OR used in dependency arrays |
Key rule: useMemo/useCallback are pointless without React.memo on the receiving child (or a dependency array consumer).
When NOT to Memoize
- Simple/cheap components that render fast
- Props that change on almost every render anyway
- Components without memoized children receiving the value
- Simple calculations (the comparison overhead exceeds computation cost)
React Compiler
The React Compiler is a Babel plugin that automatically applies memoization at build time. It analyzes your code and inserts useMemo/useCallback where safe.
Prerequisites: Code must follow the Rules of React — components must be pure, props/state immutable, no side effects in render.
What the Compiler Handles
- Stabilizing callback identities (replaces manual
useCallback) - Memoizing derived values (replaces manual
useMemo) - Memoizing JSX output (replaces many
React.memowrappers)
What You Still Need Manually
React.memo: Impure components, 3rd-party library components, explicit render boundariesuseMemo: Truly expensive computations the compiler can't prove safe, custom equality logicuseCallback: Complex closure semantics, library APIs requiring stable function identities
Context API Performance
Context is a broadcast mechanism — every consumer re-renders when the value changes. The fix is splitting.
The Two-Context Pattern
Separate state from dispatch/actions into two contexts so components that only dispatch never re-render when state changes (full Provider example in references/context-optimization.md).
Split by Domain
Never create a "mega context" with unrelated state. Split into ThemeContext, AuthContext, UIContext, etc.
Concurrent Features
useTransition vs useDeferredValue
useTransition | useDeferredValue | |
|---|---|---|
| Wraps | The action (setState call) | The value (result) |
| Use when | You control the state update | You don't control the update |
| Provides | isPending boolean | Compare current vs deferred value |
| Effect | Marks update as low-priority | Value lags behind during urgent updates |
Neither makes anything faster — they make the UI feel faster by prioritizing urgent updates (typing) over expensive work (filtering). Code examples for both, plus Suspense and data-fetching patterns, live in references/concurrent-features.md.
Bundle Performance
Code Splitting Checklist
- Route-based splitting —
lazy()+Suspensefor each route - Heavy component splitting — Lazy-load modals, charts, editors
- Conditional feature splitting — Admin panels, premium features
- Bundle analysis — Use
webpack-bundle-analyzerorsource-map-explorer - Tree shaking — Use named exports, check
sideEffectsin package.json
Core Web Vitals Quick Reference
| Metric | Target | React Optimization |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | SSR/SSG, preload critical assets, optimize images |
| INP (Interaction to Next Paint) | < 200ms | Break long tasks, memoize, debounce/throttle, Web Workers |
| CLS (Cumulative Layout Shift) | < 0.1 | Set image dimensions, reserve space for async content, skeleton loaders |
React Fiber — How Rendering Works
React Fiber is a cooperatively-scheduled rendering engine using a linked-list tree structure.
- Two trees: Current (what DOM reflects) and Work-in-Progress (draft being prepared)
- Yielding: React yields to browser every ~5ms, allowing paint/input handling
- Lanes: Priority system using bitmasks —
SyncLane>InputContinuousLane>DefaultLane>TransitionLane>IdleLane - Commits are never interrupted — once render phase completes, DOM updates happen synchronously
Understanding this helps explain why useTransition works: it assigns updates to lower-priority lanes.
References
Each file is loaded on demand — read one only when the task needs that depth (progressive disclosure).
references/profiling-and-debugging.md— the systematic measure-before-optimize workflow, the React DevTools Profiler, the Chrome Performance tab, and production-profiling build setup · read when measuring, reproducing, or diagnosing a slowdown before touching code.references/memoization-patterns.md—React.memo,useMemo, anduseCallbackin depth with custom comparators and the dependency-array rules · read when applying memoization or deciding which of the three to reach for.references/react-compiler.md— React Compiler setup, how it auto-memoizes, migration steps, and what still needs manual memoization · read when adopting/migrating to the React Compiler or auditing what it can't cover.references/context-optimization.md— the two-context (state/dispatch) Provider pattern, the mega-context anti-pattern, and domain splitting · read when a Context value re-renders too many consumers.references/concurrent-features.md—useTransition,useDeferredValue, and Suspense with full code and data-fetching patterns · read when making expensive updates non-blocking or wiring Suspense.references/bundle-and-loading.md— code splitting,lazy()/Suspenseroute and component splitting, lazy-loading patterns, tree shaking, and bundle analysis · read when reducing bundle size or implementing lazy loading.
What ships with it: 6 files
33.9 KB alongside SKILL.md
references/
- bundle-and-loading.md5.4 KB
- concurrent-features.md5.7 KB
- context-optimization.md5.4 KB
- memoization-patterns.md5.3 KB
- profiling-and-debugging.md5.6 KB
- react-compiler.md6.5 KB
Gives 0 of the 12 instructions most performance cost skills give in ~1.9k tokens
Counted across 803 of the 1,058 authors here whose files we hold, read 2026-08-07
- Keep skill files under 500 lines or tokensin 82 of 803, across 16 files
- Use imperative form in instructionsin 80 of 803, across 9 files
- Draft assertions while test runs are in progressin 75 of 803, across 9 files
- Create two to three realistic test promptsin 74 of 803, across 9 files
- Write skill descriptions to be pushyin 72 of 803, across 7 files
- Save test cases to evals JSONin 72 of 803, across 6 files
- Ask questions about edge cases and input formatsin 72 of 803, across 7 files
- Save timing data immediately when runs completein 70 of 803, across 5 files
- Include all trigger conditions in the skill descriptionin 69 of 803, across 3 files
- Launch all test runs in a single turn or simultaneouslyin 69 of 803, across 3 files
- Capture intent before writing a skillin 67 of 803, across 1 file
- Import directly instead of barrel filesin 52 of 803, across 15 files
Said here and by no other author read
- profile production builds only
- simulate real user conditions during profiling
- derive values instead of storing them
- split contexts by domain
- read referenced files on demand for depth
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.