Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add excatt/superclaude-plusplus --skill "react-best-practices"
Install specific skill from multi-skill repository
# Description
Vercel Engineering's React & Next.js performance optimization guide. Use when writing React components, implementing data fetching, reviewing code for performance, refactoring, or optimizing bundle size. Contains 40+ rules across 8 priority categories.
# SKILL.md
name: react-best-practices
description: Vercel Engineering's React & Next.js performance optimization guide. Use when writing React components, implementing data fetching, reviewing code for performance, refactoring, or optimizing bundle size. Contains 40+ rules across 8 priority categories.
source: https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices
version: 1.0.0
Vercel React Best Practices
Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel Engineering. Contains 40+ rules across 8 categories prioritized by impact.
When to Apply
Reference these guidelines when:
- Writing new React components or Next.js pages
- Implementing data fetching patterns
- Reviewing code for performance issues
- Refactoring existing code
- Optimizing bundle size or load times
Priority 0: Package Management (MANDATORY)
필수 규칙: pnpm 사용 (npm, yarn 금지)
Rule 0.1: Package Manager
| 항목 | 규칙 |
|---|---|
| 패키지 매니저 | pnpm (필수) |
| Lock 파일 | pnpm-lock.yaml (커밋 필수) |
| 워크스페이스 | pnpm-workspace.yaml (모노레포) |
# ✅ Correct
pnpm add react next
pnpm add -D typescript @types/react
pnpm install --frozen-lockfile
# ❌ Incorrect
npm install react next
yarn add react next
Rule 0.2: Dockerfile Pattern
# ✅ Correct - pnpm in Docker
FROM node:20-slim
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY . .
CMD ["pnpm", "start"]
Rule 0.3: CI/CD Pattern (GitHub Actions)
# ✅ Correct - pnpm in CI
- uses: pnpm/action-setup@v2
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm build
Detection: package-lock.json 또는 yarn.lock 발견 시 pnpm으로 마이그레이션 제안
Priority 1: Eliminating Waterfalls (CRITICAL)
Impact: 2-10x performance improvement
Rule 1.1: Defer await to Point of Use
Move await statements to where the data is actually needed.
// ❌ Incorrect - blocks entire function
async function Page() {
const data = await fetchData();
const user = await fetchUser();
return <Component data={data} user={user} />;
}
// ✅ Correct - parallel fetching
async function Page() {
const dataPromise = fetchData();
const userPromise = fetchUser();
return <Component dataPromise={dataPromise} userPromise={userPromise} />;
}
Rule 1.2: Use Promise.all for Independent Operations
Execute independent operations concurrently.
// ❌ Incorrect - sequential
const users = await getUsers();
const posts = await getPosts();
const comments = await getComments();
// ✅ Correct - parallel
const [users, posts, comments] = await Promise.all([
getUsers(),
getPosts(),
getComments()
]);
Rule 1.3: Dependency-Based Parallelization
Use better-all for operations with complex dependencies.
import { all } from 'better-all';
// ✅ Correct - handles dependencies automatically
const { user, posts, notifications } = await all({
user: () => fetchUser(userId),
posts: ({ user }) => fetchPosts(user.id),
notifications: ({ user }) => fetchNotifications(user.id)
});
Rule 1.4: Strategic Suspense Boundaries
Deploy Suspense boundaries for faster initial paint.
// ✅ Correct - progressive loading
function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<Skeleton />}>
<SlowContent /> {/* Streams in when ready */}
</Suspense>
</div>
);
}
Priority 2: Bundle Size Optimization (CRITICAL)
Impact: 200-800ms faster load times
Rule 2.1: Avoid Barrel File Imports
Use direct source imports instead of barrel files.
// ❌ Incorrect - imports entire barrel
import { Button } from '@/components';
import { formatDate } from '@/utils';
// ✅ Correct - direct imports
import { Button } from '@/components/Button';
import { formatDate } from '@/utils/formatDate';
Rule 2.2: Dynamic Import Heavy Components
Lazy-load components that aren't immediately needed.
import dynamic from 'next/dynamic';
// ✅ Correct - lazy load heavy component
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false // Skip SSR for client-only components
});
Rule 2.3: Defer Third-Party Scripts
Load non-critical scripts after hydration.
import Script from 'next/script';
// ✅ Correct - deferred loading
<Script
src="https://analytics.example.com/script.js"
strategy="lazyOnload"
/>
Rule 2.4: Conditional Module Loading
Load modules only when features are active.
// ✅ Correct - load only when needed
async function handleAdvancedFeature() {
const { advancedFunction } = await import('./advancedModule');
return advancedFunction();
}
Rule 2.5: Intent-Based Preloading
Preload bundles based on user intent.
// ✅ Correct - preload on hover
function NavLink({ href, children }) {
const handleMouseEnter = () => {
import(`./pages${href}`); // Preload the route
};
return (
<Link href={href} onMouseEnter={handleMouseEnter}>
{children}
</Link>
);
}
Priority 3: Server-Side Performance (HIGH)
Rule 3.1: Use React.cache for Request Deduplication
Deduplicate data fetching within a single request.
import { cache } from 'react';
// ✅ Correct - cached per request
export const getUser = cache(async (id: string) => {
return await db.user.findUnique({ where: { id } });
});
Rule 3.2: Cross-Request LRU Caching
Cache expensive computations across requests.
import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, any>({ max: 500 });
// ✅ Correct - cross-request cache
export async function getExpensiveData(key: string) {
if (cache.has(key)) return cache.get(key);
const data = await computeExpensive(key);
cache.set(key, data);
return data;
}
Rule 3.3: Minimize RSC Serialization
Reduce data passed across RSC boundaries.
// ❌ Incorrect - passes entire object
<ClientComponent user={fullUserObject} />
// ✅ Correct - minimal data
<ClientComponent
userName={user.name}
userAvatar={user.avatar}
/>
Rule 3.4: Parallel Data Fetching via Composition
Structure components to enable parallel fetching.
// ✅ Correct - parallel through composition
function Page() {
return (
<>
<UserSection userId={id} /> {/* fetches user */}
<PostsSection userId={id} /> {/* fetches posts in parallel */}
<CommentsSection postId={pid} /> {/* fetches comments in parallel */}
</>
);
}
Rule 3.5: Non-Blocking Operations with after()
Schedule operations that don't block the response.
import { after } from 'next/server';
// ✅ Correct - non-blocking analytics
export async function GET() {
const data = await fetchData();
after(() => {
logAnalytics(data); // Runs after response sent
});
return Response.json(data);
}
Priority 4: Client-Side Data Fetching (MEDIUM-HIGH)
Rule 4.1: Automatic Deduplication with SWR
Use SWR for client-side data fetching with built-in deduplication.
import useSWR from 'swr';
// ✅ Correct - deduplicated across components
function useUser(id: string) {
return useSWR(`/api/user/${id}`, fetcher);
}
Rule 4.2: useSWRSubscription for Event Listeners
Deduplicate global event listeners.
import useSWRSubscription from 'swr/subscription';
// ✅ Correct - single listener, shared state
function useOnlineStatus() {
return useSWRSubscription('online-status', (key, { next }) => {
const handler = () => next(null, navigator.onLine);
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
return () => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
};
});
}
Priority 5: Re-render Optimization (MEDIUM)
Rule 5.1: Defer State Reads
Read state at the point of use, not at the top of components.
// ❌ Incorrect - reads at top, re-renders entire tree
function Parent() {
const count = useStore(state => state.count);
return <Child count={count} />;
}
// ✅ Correct - reads where needed
function Parent() {
return <Child />;
}
function Child() {
const count = useStore(state => state.count);
return <span>{count}</span>;
}
Rule 5.2: Extract Work into Memoized Components
Isolate expensive renders.
// ✅ Correct - isolated expensive render
const ExpensiveList = memo(function ExpensiveList({ items }) {
return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});
function Parent({ items, otherState }) {
return (
<div>
<ExpensiveList items={items} />
<CheapComponent state={otherState} />
</div>
);
}
Rule 5.3: Narrow Effect Dependencies
Use primitives instead of objects in dependency arrays.
// ❌ Incorrect - object reference changes
useEffect(() => {
fetchData(user);
}, [user]);
// ✅ Correct - stable primitive
useEffect(() => {
fetchData(userId);
}, [userId]);
Rule 5.4: Functional setState Updates
Prevent stale closures with functional updates.
// ❌ Incorrect - may use stale count
setCount(count + 1);
// ✅ Correct - always uses current value
setCount(prev => prev + 1);
Rule 5.5: Lazy State Initialization
Defer expensive initial state computation.
// ❌ Incorrect - runs every render
const [data, setData] = useState(expensiveComputation());
// ✅ Correct - runs once
const [data, setData] = useState(() => expensiveComputation());
Rule 5.6: Mark Non-Urgent Updates as Transitions
Use transitions for non-critical updates.
import { useTransition } from 'react';
function SearchResults() {
const [isPending, startTransition] = useTransition();
const handleSearch = (query) => {
startTransition(() => {
setSearchResults(filterResults(query));
});
};
}
Priority 6: Rendering Performance (MEDIUM)
Rule 6.1: Hoist Static JSX
Move static elements outside components.
// ✅ Correct - static JSX hoisted
const StaticHeader = <header><h1>Welcome</h1></header>;
function Page() {
return (
<div>
{StaticHeader}
<DynamicContent />
</div>
);
}
Rule 6.2: Use content-visibility for Long Lists
Apply CSS containment for off-screen content.
/* ✅ Correct - skip rendering off-screen items */
.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 50px;
}
Rule 6.3: Activity Components for Frequent Toggles
Use Activity (or custom equivalent) for frequently shown/hidden content.
// ✅ Correct - preserves state, skips unmount/remount
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<ExpensiveComponent />
</Activity>
Rule 6.4: Hardware-Accelerated SVG Animation
Animate wrapper elements for better performance.
// ✅ Correct - hardware acceleration
<motion.div animate={{ x: 100 }}>
<svg>{/* static SVG content */}</svg>
</motion.div>
Rule 6.5: Explicit Conditional Rendering
Use ternary operators for clearer render paths.
// ❌ Incorrect - implicit falsy render
{items.length && <List items={items} />}
// ✅ Correct - explicit conditional
{items.length > 0 ? <List items={items} /> : null}
Priority 7: JavaScript Performance (LOW-MEDIUM)
Rule 7.1: Build Index Maps for Lookups
Replace repeated array searches with Map lookups.
// ❌ Incorrect - O(n) per lookup
const user = users.find(u => u.id === targetId);
// ✅ Correct - O(1) lookup
const userMap = new Map(users.map(u => [u.id, u]));
const user = userMap.get(targetId);
Rule 7.2: Use Set for Membership Checks
O(1) instead of O(n) for includes checks.
// ❌ Incorrect - O(n)
if (allowedIds.includes(id)) { }
// ✅ Correct - O(1)
const allowedSet = new Set(allowedIds);
if (allowedSet.has(id)) { }
Rule 7.3: Cache Property Access in Loops
Avoid repeated property lookups.
// ❌ Incorrect - repeated access
for (let i = 0; i < arr.length; i++) {
process(obj.nested.value);
}
// ✅ Correct - cached access
const { length } = arr;
const { value } = obj.nested;
for (let i = 0; i < length; i++) {
process(value);
}
Rule 7.4: Batch DOM CSS Changes
Use classes or cssText for multiple style changes.
// ❌ Incorrect - multiple reflows
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';
// ✅ Correct - single reflow
el.classList.add('active-state');
// or
el.style.cssText = 'width:100px;height:100px;margin:10px';
Rule 7.5: Hoist RegExp Outside Renders
Avoid recreating RegExp on every render.
// ❌ Incorrect - creates new RegExp each render
function Component({ value }) {
const isValid = /^[a-z]+$/.test(value);
}
// ✅ Correct - single instance
const ALPHA_REGEX = /^[a-z]+$/;
function Component({ value }) {
const isValid = ALPHA_REGEX.test(value);
}
Rule 7.6: Early Returns
Exit functions early when possible.
// ✅ Correct - early return pattern
function processUser(user) {
if (!user) return null;
if (!user.active) return null;
return performExpensiveOperation(user);
}
Rule 7.7: Prefer .toSorted() Over .sort()
Avoid mutating original arrays.
// ❌ Incorrect - mutates original
const sorted = items.sort((a, b) => a.id - b.id);
// ✅ Correct - returns new array
const sorted = items.toSorted((a, b) => a.id - b.id);
Priority 8: Advanced Patterns (LOW)
Rule 8.1: Store Event Handlers in Refs
Stable subscriptions without effect re-runs.
// ✅ Correct - stable callback reference
const callbackRef = useRef(callback);
callbackRef.current = callback;
useEffect(() => {
return subscribe(() => callbackRef.current());
}, []); // Empty deps - never re-subscribes
Rule 8.2: useEffectEvent for Fresh Callbacks
Access latest values without re-running effects.
// ✅ Correct - stable effect with fresh values
const onTick = useEffectEvent(() => {
logCurrentState(state); // Always has latest state
});
useEffect(() => {
const id = setInterval(onTick, 1000);
return () => clearInterval(id);
}, []); // Never re-creates interval
Important Caveats
-
React Compiler: Some optimizations (memo, useMemo hoisting) are handled automatically by React Compiler when enabled.
-
Measure First: Apply optimizations based on measured bottlenecks, not assumptions.
-
Context Matters: Pattern selection depends on specific use cases and trade-offs.
-
Progressive Enhancement: Start with critical optimizations (Priority 1-2) before lower priorities.
Tools Referenced
- better-all: Dependency-based parallelization
- lru-cache: Cross-request caching
- SWR: Client-side data fetching with deduplication
- SVGO: SVG optimization
- Motion: Animation library for React
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.