skills/react-performance/SKILL.md
React/Next.js performance patterns with wrong/right code examples. Apply when building or reviewing React components. Adapted from Vercel React Best Practices by @shuding.
npx skillsauth add adilkalam/orca react-performanceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Why: Avoid blocking paths that never use the result.
Wrong:
async function handle(id: string, skip: boolean) {
const data = await fetchData(id)
if (skip) return { skipped: true }
return process(data)
}
Right:
async function handle(id: string, skip: boolean) {
if (skip) return { skipped: true }
return process(await fetchData(id))
}
Why: Sequential awaits add full round-trip per call. Fire promises early, await when needed.
Wrong:
const session = await auth()
const config = await fetchConfig()
const data = await fetchData(session.user.id)
Right:
const sessionP = auth(), configP = fetchConfig()
const session = await sessionP
const [config, data] = await Promise.all([configP, fetchData(session.user.id)])
Why: N sequential awaits = N round trips; Promise.all = 1.
Wrong: const a = await f1(); const b = await f2()
Right: const [a, b] = await Promise.all([f1(), f2()])
Why: Wrap only data-dependent sections so the rest renders immediately.
Wrong:
async function Page() {
const data = await fetchData() // blocks entire page
return <div><Header /><DataView data={data} /><Footer /></div>
}
Right:
function Page() {
return <div><Header /><Suspense fallback={<Skeleton />}><DataView /></Suspense><Footer /></div>
}
async function DataView() { const data = await fetchData(); return <div>{data.content}</div> }
Why: Barrel re-exports load thousands of modules (200-800ms cold start).
Wrong: import { Check } from 'lucide-react'
Right: import Check from 'lucide-react/dist/esm/icons/check'
Or use optimizePackageImports in next.config.js.
Why: Load heavy modules only when the feature activates.
Wrong: import { frames } from './animation-frames'
Right: if (enabled) import('./animation-frames').then(m => setFrames(m.frames))
Why: Analytics/logging don't block interaction. Load after hydration.
Wrong: import { Analytics } from '@vercel/analytics/react'
Right: const Analytics = dynamic(() => import('@vercel/analytics/react').then(m => m.Analytics), { ssr: false })
Why: Large components not needed initially load on demand.
Wrong: import { MonacoEditor } from './monaco-editor'
Right: const MonacoEditor = dynamic(() => import('./monaco-editor').then(m => m.MonacoEditor), { ssr: false })
Why: Start loading on hover so bundle is ready on click.
Right: <button onMouseEnter={() => void import('./editor')} onClick={open}>Open</button>
Why: React.cache is per-request. LRU shares across sequential requests.
const cache = new LRUCache<string, any>({ max: 1000, ttl: 5 * 60_000 })
async function getUser(id: string) {
return cache.get(id) ?? (() => { const p = db.user.findUnique({ where: { id } }); p.then(u => cache.set(id, u)); return p })()
}
Why: Props crossing server/client boundary are serialized. Pass only needed fields.
Wrong: <Profile user={user} /> (50 fields)
Right: <Profile name={user.name} /> (1 field)
Why: RSCs in parent-child tree fetch sequentially. Siblings parallelize.
Wrong: async function Page() { const h = await fetchHeader(); return <div>{h}<Sidebar /></div> }
Right: function Page() { return <div><Header /><Sidebar /></div> } (both fetch in parallel)
Why: Multiple components calling same function share one execution per request.
export const getUser = cache(async () => {
const s = await auth(); return s?.user?.id ? db.user.findUnique({ where: { id: s.user.id } }) : null
})
Why: Logging should not delay response.
Wrong: await updateDB(req); await log(req); return json({ ok: 1 })
Right: await updateDB(req); after(() => log(req)); return json({ ok: 1 })
Why: N hook instances share 1 listener via useSWRSubscription, not N.
Why: Multiple components fetching same key share one request.
Wrong: useEffect(() => { fetch(url).then(r => r.json()).then(set) }, [])
Right: const { data } = useSWR(url, fetcher)
Why: searchParams subscription re-renders on every URL change.
Wrong: const sp = useSearchParams(); const fn = () => sp.get('ref')
Right: const fn = () => new URLSearchParams(location.search).get('ref')
Why: Expensive work behind loading guard still runs in same component.
Wrong:
function Profile({ user, loading }) {
const av = useMemo(() => compute(user), [user])
if (loading) return <Skeleton />; return <div>{av}</div>
}
Right:
const Avatar = memo(({ user }) => <Img id={useMemo(() => compute(user), [user])} />)
function Profile({ user, loading }) { if (loading) return <Skeleton />; return <Avatar user={user} /> }
Wrong: useEffect(() => log(user.id), [user])
Right: useEffect(() => log(user.id), [user.id])
Why: Continuous values re-render every pixel; booleans only on transition.
Wrong: const w = useWindowWidth(); const m = w < 768
Right: const m = useMediaQuery('(max-width: 767px)')
Why: Prevents stale closures; removes state from deps.
Wrong: useCallback(() => setItems([...items, x]), [items])
Right: useCallback(x => setItems(c => [...c, x]), [])
Wrong: useState(buildIndex(items)) (runs every render)
Right: useState(() => buildIndex(items)) (runs once)
Wrong: setScrollY(window.scrollY) (blocks UI)
Right: startTransition(() => setScrollY(window.scrollY))
Why: No GPU acceleration on SVG elements directly.
Wrong: <svg className="animate-spin">...</svg>
Right: <div className="animate-spin"><svg>...</svg></div>
Why: Browser skips layout/paint for off-screen items.
.item { content-visibility: auto; contain-intrinsic-size: 0 80px; }
Wrong: function C() { return loading && <div className="sk" /> }
Right: const sk = <div className="sk" />; function C() { return loading && sk }
Wrong: d="M 10.293847 20.847362"
Right: d="M 10.3 20.8" (svgo --precision=1)
Use synchronous <script> to read localStorage before React hydrates instead of useEffect.
Preserves state/DOM: <Activity mode={open ? 'visible' : 'hidden'}><Menu /></Activity>
Why: {count && <Badge />} renders "0" when count is 0.
Wrong: {count && <span>{count}</span>}
Right: {count > 0 ? <span>{count}</span> : null}
Wrong: for (u of users) { if (!u.email) err = 'req' } return err ? ...
Right: for (u of users) { if (!u.email) return { error: 'req' } } return { valid: true }
Wrong: ['a','b'].includes(id) (O(n))
Right: new Set(['a','b']).has(id) (O(1))
content-media
Download YouTube video transcripts when user provides a YouTube URL or asks to download/get/fetch a transcript from YouTube. Also use when user wants to transcribe or get captions/subtitles from a YouTube video.
development
Web UI quality rules: interactions, forms, loading, animations, layout, content, performance, accessibility, design. Apply to all web UI work. Adapted from Vercel Design Guidelines.
testing
MANDATORY protocol enforcing knowledge check before EVERY response - prevents explaining systems without reading docs, claiming without verification, and ignoring auto-loaded context
testing
Typography hierarchy and spacing scale fallbacks. Yields to project design-dna when present.