Aller au contenu principal

Skill: dev-react-perf

Fork

React/Next.js performance optimization. Trigger when the user wants to optimize rendering, reduce re-renders, or improve Core Web Vitals.

Configuration

PropertyValue
Contextfork
Allowed toolsRead, Write, Edit, Bash, Glob, Grep
Keywordsdev, react, perf

Detailed description

React Performance Optimization

Avoid unnecessary re-renders

useMemo - Memoize expensive computations

const expensiveValue = useMemo(() => {
return computeExpensiveValue(items);
}, [items]);

useCallback - Memoize functions

const handleClick = useCallback(() => {
onSubmit(formData);
}, [formData, onSubmit]);

React.memo - Memoize components

const UserCard = memo(({ user }: Props) => {
return <div>{user.name}</div>;
});

Lazy Loading

// Components
const HeavyComponent = lazy(() => import('./HeavyComponent'));

<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>

// Routes (Next.js)
const DynamicComponent = dynamic(() => import('./Component'), {
loading: () => <Skeleton />,
ssr: false,
});

Virtualization

import { FixedSizeList } from 'react-window';

<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>

Images (Next.js)

import Image from 'next/image';

<Image
src="/photo.jpg"
alt="Description"
width={800}
height={600}
priority={isAboveFold}
placeholder="blur"
/>

Core Web Vitals

MetricTargetOptimization
LCP< 2.5sPreload hero image, SSR
FID< 100msCode splitting, defer JS
CLS< 0.1Explicit dimensions

Composition Patterns

Avoid excessive boolean props

// BAD: boolean props explosion
<Button primary large rounded disabled loading />

// GOOD: composition with variants
<Button variant="primary" size="large" shape="rounded" state="loading" />

// BETTER: compound components
<Button.Primary size="large">
<Button.Spinner /> Loading...
</Button.Primary>

Compound Components

// Compound component pattern
function Tabs({ children }: { children: React.ReactNode }) {
const [active, setActive] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
}

Tabs.List = function TabList({ children }) { /* ... */ };
Tabs.Tab = function Tab({ index, children }) { /* ... */ };
Tabs.Panel = function TabPanel({ index, children }) { /* ... */ };

// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}>Content 1</Tabs.Panel>
<Tabs.Panel index={1}>Content 2</Tabs.Panel>
</Tabs>

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>
);
}

Children as Props (avoid re-renders)

// GOOD: children do not re-render when the parent changes
function ScrollTracker({ children }: { children: React.ReactNode }) {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handler = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
}, []);

return (
<div>
<ScrollIndicator position={scrollY} />
{children} {/* Does NOT re-render when scrollY changes */}
</div>
);
}

Render Props vs Hooks

// PREFER hooks over render props
// BAD: render prop (verbose, nested)
<WindowSize render={({ width }) => (
<div>{width > 768 ? <Desktop />: <Mobile />}</div>
)} />

// GOOD: custom hook (simple, composable)
function ResponsiveLayout() {
const { width } = useWindowSize();
return width > 768 ? <Desktop />: <Mobile />;
}

Tools

# Analyze the bundle
npm run build -- --analyze

# Lighthouse
npx lighthouse https://example.com

# React DevTools Profiler
# Why did you render? (debug re-renders)
npm install @welldone-software/why-did-you-render

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 fork

Fork means the skill runs in an isolated context:

  • Does not pollute the main conversation
  • Results are returned cleanly
  • Ideal for autonomous tasks

See also