react-best-practices

Comprehensive performance optimization guide for React and Next.js applications. Contains 45+ rules across 8 categories, prioritized by impact.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-best-practices" with this command: npx skills add 5dlabs/cto/5dlabs-cto-react-best-practices

React Best Practices

Comprehensive performance optimization guide for React and Next.js applications. Contains 45+ rules across 8 categories, prioritized by impact.

When to Use

Reference these guidelines when:

  • Writing new React components or Next.js pages

  • Implementing data fetching (client or server-side)

  • Reviewing code for performance issues

  • Refactoring existing React/Next.js code

  • Optimizing bundle size or load times

Relevant Agents: Blaze (React/Next.js), Spark (Electron), Tap (Expo/React Native)

Rule Categories by Priority

Priority Category Impact

1 Eliminating Waterfalls CRITICAL

2 Bundle Size Optimization CRITICAL

3 Server-Side Performance HIGH

4 Client-Side Data Fetching MEDIUM-HIGH

5 Re-render Optimization MEDIUM

6 Rendering Performance MEDIUM

7 JavaScript Performance LOW-MEDIUM

  1. Eliminating Waterfalls (CRITICAL)

Waterfalls are the #1 performance killer. Fix these first.

Move await into branches

Bad:

async function handleRequest(userId: string, skipProcessing: boolean) { const userData = await fetchUserData(userId); // Always waits

if (skipProcessing) { return { skipped: true }; // Waited for nothing }

return processUserData(userData); }

Good:

async function handleRequest(userId: string, skipProcessing: boolean) { if (skipProcessing) { return { skipped: true }; // Returns immediately }

const userData = await fetchUserData(userId); return processUserData(userData); }

Parallelize independent operations

Bad:

const user = await fetchUser(id); const posts = await fetchPosts(id); const comments = await fetchComments(id); // Total time: user + posts + comments

Good:

const [user, posts, comments] = await Promise.all([ fetchUser(id), fetchPosts(id), fetchComments(id), ]); // Total time: max(user, posts, comments)

Start promises early, await late

Bad:

export async function GET() { const data = await fetchData(); // Blocks immediately const transformed = transform(data); return Response.json(transformed); }

Good:

export async function GET() { const dataPromise = fetchData(); // Start immediately // ... do other setup work ... const data = await dataPromise; // Await when needed return Response.json(transform(data)); }

Use Suspense boundaries for streaming

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

  1. Bundle Size Optimization (CRITICAL)

Every KB matters for initial load.

Import directly, avoid barrel files

Bad:

import { Button } from '@/components'; // Pulls entire barrel

Good:

import { Button } from '@/components/Button'; // Only Button

Use dynamic imports for heavy components

Bad:

import { HeavyChart } from './HeavyChart';

function Dashboard() { return showChart ? <HeavyChart /> : null; }

Good:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('./HeavyChart'), { loading: () => <ChartSkeleton />, });

function Dashboard() { return showChart ? <HeavyChart /> : null; }

Defer third-party scripts

Bad:

import { Analytics } from '@analytics/lib';

function App() { useEffect(() => { Analytics.init(); // Blocks hydration }, []); }

Good:

function App() { useEffect(() => { // Load after hydration import('@analytics/lib').then(({ Analytics }) => { Analytics.init(); }); }, []); }

Preload on hover/focus

function Link({ href, children }) { const preload = () => { const link = document.createElement('link'); link.rel = 'prefetch'; link.href = href; document.head.appendChild(link); };

return ( <a href={href} onMouseEnter={preload} onFocus={preload}> {children} </a> ); }

  1. Server-Side Performance (HIGH)

Use React.cache() for per-request deduplication

import { cache } from 'react';

const getUser = cache(async (id: string) => { return await db.user.findUnique({ where: { id } }); });

// Multiple components can call getUser(id) - only one DB query

Minimize data passed to client components

Bad:

// Server Component async function UserPage({ id }) { const user = await getFullUser(id); // 50 fields return <ClientProfile user={user} />; }

Good:

// Server Component async function UserPage({ id }) { const user = await getFullUser(id); return ( <ClientProfile name={user.name} avatar={user.avatar} // Only what client needs /> ); }

Use after() for non-blocking operations

import { after } from 'next/server';

export async function POST(request: Request) { const data = await request.json(); const result = await saveToDb(data);

after(async () => { await sendAnalytics(result); await notifyWebhooks(result); });

return Response.json(result); // Returns immediately }

  1. Client-Side Data Fetching (MEDIUM-HIGH)

Use SWR for automatic deduplication

import useSWR from 'swr';

function useUser(id: string) { return useSWR(/api/users/${id}, fetcher, { dedupingInterval: 2000, // Dedup requests within 2s }); }

// Multiple components using useUser(same-id) = one request

Deduplicate global event listeners

Bad:

function Component() { useEffect(() => { window.addEventListener('resize', handler); // Each instance adds one return () => window.removeEventListener('resize', handler); }, []); }

Good:

// Shared hook with ref counting const listeners = new Set();

function useWindowResize(handler: () => void) { useEffect(() => { listeners.add(handler); if (listeners.size === 1) { window.addEventListener('resize', notifyAll); } return () => { listeners.delete(handler); if (listeners.size === 0) { window.removeEventListener('resize', notifyAll); } }; }, [handler]); }

  1. Re-render Optimization (MEDIUM)

Don't subscribe to state only used in callbacks

Bad:

function Form() { const [value, setValue] = useState(''); // Re-renders on every keystroke

const handleSubmit = () => { submitForm(value); };

return <input onChange={e => setValue(e.target.value)} />; }

Good:

function Form() { const valueRef = useRef('');

const handleSubmit = () => { submitForm(valueRef.current); };

return <input onChange={e => { valueRef.current = e.target.value }} />; }

Extract expensive work into memoized components

Bad:

function Parent({ data, filter }) { return ( <div> <ExpensiveList data={data} /> {/* Re-renders when filter changes */} <Filter value={filter} /> </div> ); }

Good:

const MemoizedList = memo(ExpensiveList);

function Parent({ data, filter }) { return ( <div> <MemoizedList data={data} /> {/* Only re-renders when data changes */} <Filter value={filter} /> </div> ); }

Use functional setState for stable callbacks

Bad:

const increment = useCallback(() => { setCount(count + 1); // Dependency on count }, [count]);

Good:

const increment = useCallback(() => { setCount(c => c + 1); // No dependencies }, []);

Use startTransition for non-urgent updates

import { startTransition } from 'react';

function SearchBox() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]);

const handleChange = (e) => { setQuery(e.target.value); // Urgent: update input

startTransition(() => {
  setResults(search(e.target.value));  // Non-urgent: can be interrupted
});

}; }

  1. Rendering Performance (MEDIUM)

Animate wrapper divs, not SVG elements

Bad:

<motion.svg animate={{ scale: 1.2 }}> {/* Triggers SVG recalc */} <path d="..." /> </motion.svg>

Good:

<motion.div animate={{ scale: 1.2 }}> {/* GPU accelerated */} <svg><path d="..." /></svg> </motion.div>

Use content-visibility for long lists

.list-item { content-visibility: auto; contain-intrinsic-size: 0 50px; }

Extract static JSX outside components

Bad:

function Component() { return ( <div> <header>Static Header</header> {/* Recreated every render */} <DynamicContent /> </div> ); }

Good:

const StaticHeader = <header>Static Header</header>;

function Component() { return ( <div> {StaticHeader} {/* Same reference */} <DynamicContent /> </div> ); }

Use ternary, not && for conditionals

Bad:

{items.length && <List items={items} />} // Renders "0" when empty

Good:

{items.length > 0 ? <List items={items} /> : null}

  1. JavaScript Performance (LOW-MEDIUM)

Build Map for repeated lookups

Bad:

users.forEach(user => { const role = roles.find(r => r.userId === user.id); // O(n) each time });

Good:

const roleMap = new Map(roles.map(r => [r.userId, r])); users.forEach(user => { const role = roleMap.get(user.id); // O(1) });

Combine multiple iterations

Bad:

const active = users.filter(u => u.active); const names = active.map(u => u.name); const sorted = names.sort(); // 3 iterations

Good:

const names = []; for (const u of users) { if (u.active) names.push(u.name); } names.sort(); // 1 iteration + sort

Check length before expensive operations

Bad:

if (items.some(item => expensiveCheck(item))) { ... }

Good:

if (items.length > 0 && items.some(item => expensiveCheck(item))) { ... }

Use Set/Map for O(1) lookups

Bad:

const isSelected = selectedIds.includes(id); // O(n)

Good:

const selectedSet = new Set(selectedIds); const isSelected = selectedSet.has(id); // O(1)

Quick Reference Checklist

Before submitting React/Next.js code:

Critical (Fix First)

  • No sequential awaits for independent operations

  • Dynamic imports for components >50KB

  • No barrel file imports in hot paths

  • Third-party scripts loaded after hydration

High Priority

  • Server components minimize client-passed data

  • React.cache() for repeated data fetches

  • Proper Suspense boundaries

Medium Priority

  • Memoized expensive components

  • Stable callback references (functional setState)

  • No unnecessary re-renders from state subscriptions

Related Skills

  • test-driven-development - TDD for React components

  • verification-before-completion - Verify bundle size, performance metrics

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

expo-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

better-auth-expo

No summary provided by upstream source.

Repository SourceNeeds Review
General

elysia-llm-docs

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-excellence

No summary provided by upstream source.

Repository SourceNeeds Review