react-best-practices
SKILL.md
React & Next.js Performance — Vercel Best Practices
45 rules across 8 categories, ordered by impact. Apply CRITICAL rules first.
When to Use
- Optimizing React or Next.js application performance
- Reviewing code for performance bottlenecks
- Fixing waterfalls, large bundles, or unnecessary re-renders
- Building new features with performance in mind
1. Eliminating Waterfalls (CRITICAL)
Sequential async operations are the #1 performance killer.
| Rule | Pattern |
|---|---|
| async-defer-await | Don't await unless you need the result before continuing |
| async-parallel | Use Promise.all() for independent fetches |
| async-dependencies | Only await what depends on previous result |
| async-api-routes | Parallel fetch in API routes, not just components |
| async-suspense | Wrap async components in Suspense for parallel streaming |
// BAD: Sequential waterfall
const user = await getUser(id);
const posts = await getPosts(id); // Waits for user unnecessarily
const comments = await getComments(id);
// GOOD: Parallel fetching
const [user, posts, comments] = await Promise.all([
getUser(id), getPosts(id), getComments(id),
]);
// GOOD: Suspense boundaries for parallel streaming
<Suspense fallback={<UserSkeleton />}>
<UserProfile id={id} />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<UserPosts id={id} />
</Suspense>
2. Bundle Size (CRITICAL)
| Rule | Action |
|---|---|
| no-barrel-imports | Import from specific files, not barrel index.ts |
| dynamic-import | next/dynamic or React.lazy() for heavy components |
| defer-third-party | @next/third-parties or lazy-load analytics/chat/maps |
| conditional-load | Load features only when user needs them |
| preload-critical | <link rel="preload"> for above-the-fold resources |
// BAD: Barrel import pulls entire library
import { Button } from '@/components';
// GOOD: Direct import
import { Button } from '@/components/ui/button';
// GOOD: Dynamic import for heavy components
const Chart = dynamic(() => import('@/components/chart'), {
loading: () => <ChartSkeleton />,
ssr: false,
});
3. Server-Side Performance (HIGH)
| Rule | Action |
|---|---|
| react-cache | React.cache() for request-scoped dedup |
| lru-cache | LRU cache for cross-request data (DB queries) |
| parallel-fetch | Fetch data in parallel in server components |
| after() | next/server after() for post-response work (logging) |
// Deduplicate within a single request
const getUser = React.cache(async (id: string) => {
return await db.user.findUnique({ where: { id } });
});
// Cross-request cache for expensive queries
import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, Product[]>({ max: 100, ttl: 60_000 });
4. Client-Side Data Fetching (MEDIUM-HIGH)
| Rule | Action |
|---|---|
| swr-dedup | Use SWR/React Query — automatic request dedup + cache |
| event-listeners | Clean up listeners in useEffect return |
const { data, isLoading } = useSWR(`/api/user/${id}`, fetcher);
5. Re-render Optimization (MEDIUM)
Apply only after profiling confirms re-render issues.
| Rule | Action |
|---|---|
| defer-reads | Don't read context/store in parent; push reads to children |
| memo | React.memo() for expensive components with stable props |
| check-deps | Verify useEffect/useMemo/useCallback dependency arrays |
| derived-state | Compute during render, not in state + useEffect |
| functional-setState | setCount(c => c + 1) not setCount(count + 1) |
| lazy-state-init | useState(() => expensive()) not useState(expensive()) |
| transitions | useTransition / useDeferredValue for non-urgent updates |
// BAD: Derived state in useEffect (extra render cycle)
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);
// GOOD: Compute during render
const count = items.length;
// BAD: Expensive init runs every render
const [data] = useState(parseHugeJSON(raw));
// GOOD: Lazy initializer runs once
const [data] = useState(() => parseHugeJSON(raw));
6. Rendering Performance (MEDIUM)
| Rule | Action |
|---|---|
| content-visibility | content-visibility: auto for off-screen sections |
| hoist-jsx | Extract static JSX outside component to avoid recreation |
| svg-precision | Round SVG coordinates to 1 decimal, strip metadata |
| hydration | Minimize client components; prefer Server Components |
| conditional-render | Early return before expensive JSX |
// Hoist static JSX outside component
const EMPTY_STATE = <div className="empty">No results found</div>;
function SearchResults({ results }) {
if (!results.length) return EMPTY_STATE;
return <ResultList items={results} />;
}
7. JavaScript Performance (LOW-MEDIUM)
| Rule | Action |
|---|---|
| batch-dom | Batch DOM reads then writes (avoid layout thrashing) |
| index-maps | new Map() for O(1) lookups instead of .find() |
| cache-access | Cache object.deeply.nested.value in a variable |
| set-lookups | Set.has() instead of array.includes() for large lists |
| early-exit | Return early from loops/functions when result found |
| hoist-regexp | Declare RegExp outside loops and hot functions |
// BAD: O(n) lookup in render
const selected = items.find(i => i.id === selectedId);
// GOOD: O(1) lookup with Map
const itemMap = useMemo(() => new Map(items.map(i => [i.id, i])), [items]);
const selected = itemMap.get(selectedId);
8. Advanced Patterns (LOW)
| Rule | Action |
|---|---|
| event-handler-refs | Stable callback refs to avoid child re-renders |
| useLatest | Ref-based hook for always-current value without deps |
function useEventCallback<T extends (...args: any[]) => any>(fn: T): T {
const ref = useRef(fn);
ref.current = fn;
return useCallback((...args: any[]) => ref.current(...args), []) as T;
}
Optimization Order
- Fix waterfalls — Parallel fetch, Suspense boundaries
- Reduce bundle — Tree-shake, dynamic import, no barrel imports
- Server Components — Move data fetching to server, minimize client JS
- Cache — React.cache for request dedup, LRU for cross-request
- Profile re-renders — React DevTools Profiler, then apply memo/transitions
- Micro-optimize — Only after the above are addressed
Performance Checklist
- No sequential fetches that could be parallel
- Suspense boundaries around async components
- No barrel imports from large libraries
- Heavy components lazy-loaded with dynamic()
- Third-party scripts deferred or lazy-loaded
- Server Components used where possible
- No derived state computed in useEffect
- React.memo only on profiled-slow components
- Dependency arrays correct (no missing/extra deps)
- Large lists virtualized (react-window, tanstack-virtual)
Weekly Installs
2
Repository
sivag-lab/roth_mcpGitHub Stars
1
First Seen
4 days ago
Security Audits
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2