Help us improve
Share bugs, ideas, or general feedback.
From skills
Audits React and Next.js code for six critical pitfalls: nested components, index keys, derived state in effects, unsafe fetching, unmemoized contexts, and more.
npx claudepluginhub kriscard/skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/skills:reactThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Focused audit layer for React and Next.js. Start with the six universal checks
Applies Vercel Engineering's 57 React/Next.js performance rules to eliminate waterfalls, reduce bundle size, fix re-renders, and optimize data fetching when writing, reviewing, or refactoring code.
Optimizes React 18/19 and Next.js performance with 70+ rules across 8 priority categories: waterfalls, bundle size, server/client fetching, re-renders, rendering, JS micro-performance, and advanced patterns.
Provides 70+ React/Next.js performance optimization rules across 8 priority categories (waterfalls, bundle size, server-side, client fetching, re-render, rendering, JS micro-perf, advanced). Use when writing, reviewing, or refactoring React/Next.js code for performance.
Share bugs, ideas, or general feedback.
Focused audit layer for React and Next.js. Start with the six universal checks — these are the highest-leverage bugs and appear in almost every codebase. Then route to the reference that matches the user's specific question.
These six checks take seconds to scan for and catch the most common, highest-impact mistakes. Skip them only if you have already verified them in the same session.
Components defined inside other components
They are re-created on every render. Their state resets. All memoization is
lost. React.memo on them does nothing. Extract to module scope.
Array index as key in dynamic lists
key is React's identity anchor. When the list reorders, inserts, or
deletes, an index-based key reassigns identity to the wrong element — causing
visual artifacts, wrong focus, and lost form state. Use a stable ID from the
data. Exception: truly static, never-reordered lists where index is fine.
Derived state via useState + useEffect
// ❌ Two state variables + an Effect that syncs them
const [filteredItems, setFilteredItems] = useState(items);
useEffect(() => { setFilteredItems(items.filter(f)); }, [items, f]);
// ✅ One variable, computed during render — no Effect, no lag
const filteredItems = useMemo(() => items.filter(f), [items, f]);
// or just: const filteredItems = items.filter(f); (if not expensive)
Data fetching in useEffect without cleanup
Without cleanup, a slow request that resolves after a faster one will
overwrite the fresh data with stale data (race condition):
// ❌ Race condition
useEffect(() => {
fetchUser(id).then(setUser);
}, [id]);
// ✅ Cleanup flag
useEffect(() => {
let ignore = false;
fetchUser(id).then(data => { if (!ignore) setUser(data); });
return () => { ignore = true; };
}, [id]);
// ✅ Best — use TanStack Query or RSC. Handles this automatically.
Unmemoized Context provider value Every time the parent re-renders, a new object reference is created, and every consumer re-renders even if the data hasn't changed:
// ❌ New object every parent render
<UserContext.Provider value={{ user, login, logout }}>
// ✅ Stable reference
const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);
<UserContext.Provider value={value}>
// ✅ Or split data and API into separate contexts (API never changes)
<UserDataContext.Provider value={user}>
<UserAPIContext.Provider value={api}> {/* stable — defined outside render */}
Server Actions for client-side data reads Server Actions serialize calls — they can't run in parallel. Using them for reads kills the performance of any page that needs multiple data sources:
// ❌ Server Action reads — serialize even under Promise.all
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
// These still serialize because Server Actions are not parallelizable
// ✅ Server Actions for mutations only. Reads via RSC, API routes,
// or TanStack Query.
React.FC and forwardRef — both deprecated patterns
React.FC adds an implicit children prop (wrong in React 18+), forces a
return type annotation, and provides no benefit over plain functions. In React
19, ref is a regular prop — forwardRef still works but is unnecessary for
new code:
// ❌ React.FC — adds noise, implies children, removed from CRA defaults
const Button: React.FC<ButtonProps> = ({ label }) => <button>{label}</button>;
// ✅ Plain function declaration
function Button({ label }: ButtonProps) {
return <button>{label}</button>;
}
// ❌ forwardRef — React 18 boilerplate, unnecessary in React 19+
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// ✅ React 19 — ref as a plain prop
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
Use this for full audits. The six universal checks above catch the most common bugs; this checklist catches the rest.
CRITICAL — Eliminating Waterfalls:
Promise.all for independent operationsCRITICAL — Bundle Size:
HIGH — Server-Side Performance:
React.cache() for per-request dedupafter() for non-blocking operationsMEDIUM-HIGH — Client Data Fetching:
localStorage versioned and minimalMEDIUM — Re-render Optimization:
setState updates (no stale closures)useTransition for non-urgent updatesuseEffect)useRef for transient values (mouse trackers, intervals)useMemo wrapping (prefer React Compiler if available)MEDIUM — Rendering Performance:
content-visibility for long listsuseTransition over manual loading states&& with truthy non-booleans)suppressHydrationWarning for known server/client differencesLOW-MEDIUM — JavaScript Performance:
Set/Map for O(1) lookupstoSorted() over sort() for immutabilityRegExp creation outside renderLOW — Advanced Patterns:
useEffectEvent for stable callback refsuseEffect)For each violation:
[PRIORITY] Rule Name
File: path/to/file.tsx:line
Issue: [description of the problem]
Fix: [code example showing correct pattern]
After the full checklist, provide:
Before loading references, determine which stack the user is on:
react-19.md
action props / useActionState patterns applytanstack-query.md is the first reference to reach forre-renders.md, useeffect-antipatterns.md, portals-and-stacking-context.md,
and bundle-and-perf-investigation.md are stack-agnosticIf unsure, look at the imports: next/ imports → App Router; react-router-dom
or @tanstack/react-router → Vite SPA.
Load the reference that matches the issue. Higher-priority references fix more users / cause more damage when missed — resolve CRITICAL before MEDIUM.
| Priority | Impact | Load when | Reference |
|---|---|---|---|
| 1 | CRITICAL | React.FC, forwardRef, defaultProps, React 19 APIs (useActionState, useOptimistic, use()), ref as prop, action props, nuqs, async transitions, Suspense sibling change | references/react-19.md |
| 2 | CRITICAL | sequential await, waterfall chains, Promise.all, Suspense streaming, React.cache, after(), parallel fetching | references/waterfalls.md |
| 3 | CRITICAL | slow initial load, bundle size, barrel file imports, code splitting, rollup-plugin-visualizer, Web Vitals, dynamic import | references/bundle-and-perf-investigation.md |
| 4 | HIGH | useEffect questions, "should I use an effect", stale closure, race condition, flickering UI, useLayoutEffect | references/useeffect-antipatterns.md |
| 5 | HIGH | SSR/CSR/SSG/ISR/RSC choice, "when should I use a Server Component", hydration mismatch, suppressHydrationWarning | references/rendering-models.md |
| 6 | MEDIUM-HIGH | TanStack Query, useQuery, queryOptions, select, staleTime, gcTime, mutations, optimistic updates, query keys | references/tanstack-query.md |
| 7 | MEDIUM | useMemo, useCallback, React.memo, "why does this re-render", React Compiler, memoization decision | references/re-renders.md |
| 8 | MEDIUM | modal, dialog, tooltip, popover, z-index, "appears behind", portal, stacking context | references/portals-and-stacking-context.md |