dev-react-perf — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dev-react-perf (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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:
Promise.all), await late, stream with Suspense.import { x } from 'lodash'): import the path; load rarely used heavy UI (modals, charts, editors) with lazy() / next/dynamic.useEffect + setState: compute during render, useMemo if expensive.memo child receiving inline arrows or inline objects re-renders anyway: pass stable callbacks and hoisted constants.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.
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-patternsRecipe entry: docs/recipes/recommended-vendor-skills.md §"Vercel — vercel-labs/agent-skills".
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).
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.
// 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.
| 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.
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)@welldone-software/why-did-you-render) — logs avoidable re-renders in development.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.