Skill: dev-react-perf
React/Next.js performance optimization. Points to Vercel's react-best-practices (70 rules by impact - waterfalls, bundle, re-renders) and adds what it leaves out - list virtualization, state colocation, profiling tools, Core Web Vitals targets. Trigger when the user wants to optimize rendering, reduce re-renders, or improve Core Web Vitals.
Configuration
| Property | Value |
|---|---|
| Context | inline |
| Pre-approved tools | None declared: the project's permission settings apply |
| Trigger | keywords: dev, react, perf, vercel — |
Detailed description
React Performance (pointer + gaps)
If the vercel-react-best-practices skill is installed, invoke it now (Skill tool) and apply its rules first. Either way, check these before anything else — the first two are the vendor's CRITICAL categories, the last three the re-render traps this foundation sees most:
- Request waterfalls (vendor, CRITICAL) — independent requests awaited one after another: start them together (
Promise.all), await late, stream with Suspense. - Bundle size (vendor, CRITICAL) — whole-library or barrel imports (
import { x } from 'lodash'): import the path; load rarely used heavy UI (modals, charts, editors) withlazy()/next/dynamic. - Derived state (vendor, re-render category) — never copy props into state through
useEffect+setState: compute during render,useMemoif expensive. - Memo that cannot work (foundation) — a
memochild receiving inline arrows or inline objects re-renders anyway: pass stable callbacks and hoisted constants. - Context values (foundation) — a provider
value={{ ... }}built on every render re-renders every consumer: memoize it or split the context.
With the React Compiler enabled, items 4 and 5 are handled for you: it memoizes components and values itself (the vendor skill says the same of memo/useMemo).
Vercel Engineering publishes the canonical rule set at vercel-labs/agent-skills/skills/react-best-practices (MIT): 70 rules in 8 categories ranked by impact, starting with the two CRITICAL ones — eliminating request waterfalls and bundle size — then server/client data fetching, re-renders, rendering and JS micro-optimizations. Its companion composition-patterns covers compound components and boolean-prop explosion.
Why a pointer: the vendor maintains its rules with each React and Next.js release; this skill's former 211-line version had gone stale (FID, retired in 2024; a react-window API removed in v2). Measurements behind this change: eval/skill-triggering/FINDINGS.md.
Delegate to the vendor skill
git clone --depth 1 https://github.com/vercel-labs/agent-skills ~/dev/vendor-skills/vercel
ln -s ~/dev/vendor-skills/vercel/skills/react-best-practices ./.claude/skills/react-best-practices
ln -s ~/dev/vendor-skills/vercel/skills/composition-patterns ./.claude/skills/composition-patterns
Recipe entry: docs/recipes/recommended-vendor-skills.md §"Vercel — vercel-labs/agent-skills".
What the vendor skill leaves out
Virtualization, state colocation and the profiling tools below appear in none of its 72 files (it does cover content-visibility for long lists, a lighter alternative).
Long lists: virtualize
Mounting thousands of rows costs more than any memoization saves. Render only the visible window:
import { useRef } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
export function VirtualList({ items }: { items: { id: number; name: string }[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const rows = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
});
return (
<div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
<div style={{ height: rows.getTotalSize(), position: 'relative' }}>
{rows.getVirtualItems().map((row) => (
<div
key={items[row.index].id}
style={{ position: 'absolute', top: 0, width: '100%', height: row.size, transform: `translateY(${row.start}px)` }}
>
{items[row.index].name}
</div>
))}
</div>
</div>
);
}
Rows need a stable key (an id, never the index) and a known or estimated height. Under a few hundred rows, pagination or content-visibility: auto is often enough.
State colocation (push state down)
// BAD: state in the parent (re-renders everything)
function Page() {
const [search, setSearch] = useState('');
return (
<div>
<SearchBar value={search} onChange={setSearch} />
<ExpensiveList /> {/* Unnecessary re-render! */}
<Footer /> {/* Unnecessary re-render! */}
</div>
);
}
// GOOD: state in the component that uses it
function Page() {
return (
<div>
<SearchSection /> {/* Internal state */}
<ExpensiveList /> {/* Not affected */}
<Footer /> {/* Not affected */}
</div>
);
}
Typical case: a clock, a timer or a search input whose state sits at the app root re-renders the whole tree on every tick or keystroke. Move that state into the small component that displays it.
When the state cannot move down (a provider, a layout that owns it), lift the expensive content up instead: pass it as children. Elements created by the parent are not re-created when the wrapper's state changes, so they do not re-render.
Core Web Vitals targets
| Metric | Good | Needs work | Poor |
|---|---|---|---|
| LCP | < 2.5s | 2.5–4s | > 4s |
| INP | < 200ms | 200–500ms | > 500ms |
| CLS | < 0.1 | 0.1–0.25 | > 0.25 |
Measure before and after a change; the qa-perf skill holds the measurement workflow.
Profiling tools
npx lighthouse https://example.com --view # lab LCP / INP proxy (TBT) / CLS
ANALYZE=true npm run build # bundle composition (Next.js, with @next/bundle-analyzer wired in next.config)
- React DevTools Profiler — which components rendered, why, and for how long.
why-did-you-render(@welldone-software/why-did-you-render) — logs avoidable re-renders in development.
Automatic triggering
This skill is automatically activated when:
- The matching keywords are detected in the conversation
- The task context matches the skill's domain
Triggering examples
- "I want to dev..."
- "I want to react..."
- "I want to perf..."
Context inline
Inline means the skill runs in the conversation (the default, no context key):
- Sees the full history
- Its work is visible as it happens
- Right for almost every skill