Skip to content

Commit 406d139

Browse files
feat: add 1 skill
1 parent dd3d63c commit 406d139

177 files changed

Lines changed: 18240 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent/skills/vercel-react-best-practices/AGENTS.md

Lines changed: 2934 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
---
2+
name: vercel-react-best-practices
3+
description: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
4+
license: MIT
5+
metadata:
6+
author: vercel
7+
version: "1.0.0"
8+
---
9+
10+
# Vercel React Best Practices
11+
12+
Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 57 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
13+
14+
## When to Apply
15+
16+
Reference these guidelines when:
17+
- Writing new React components or Next.js pages
18+
- Implementing data fetching (client or server-side)
19+
- Reviewing code for performance issues
20+
- Refactoring existing React/Next.js code
21+
- Optimizing bundle size or load times
22+
23+
## Rule Categories by Priority
24+
25+
| Priority | Category | Impact | Prefix |
26+
|----------|----------|--------|--------|
27+
| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
28+
| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
29+
| 3 | Server-Side Performance | HIGH | `server-` |
30+
| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
31+
| 5 | Re-render Optimization | MEDIUM | `rerender-` |
32+
| 6 | Rendering Performance | MEDIUM | `rendering-` |
33+
| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
34+
| 8 | Advanced Patterns | LOW | `advanced-` |
35+
36+
## Quick Reference
37+
38+
### 1. Eliminating Waterfalls (CRITICAL)
39+
40+
- `async-defer-await` - Move await into branches where actually used
41+
- `async-parallel` - Use Promise.all() for independent operations
42+
- `async-dependencies` - Use better-all for partial dependencies
43+
- `async-api-routes` - Start promises early, await late in API routes
44+
- `async-suspense-boundaries` - Use Suspense to stream content
45+
46+
### 2. Bundle Size Optimization (CRITICAL)
47+
48+
- `bundle-barrel-imports` - Import directly, avoid barrel files
49+
- `bundle-dynamic-imports` - Use next/dynamic for heavy components
50+
- `bundle-defer-third-party` - Load analytics/logging after hydration
51+
- `bundle-conditional` - Load modules only when feature is activated
52+
- `bundle-preload` - Preload on hover/focus for perceived speed
53+
54+
### 3. Server-Side Performance (HIGH)
55+
56+
- `server-auth-actions` - Authenticate server actions like API routes
57+
- `server-cache-react` - Use React.cache() for per-request deduplication
58+
- `server-cache-lru` - Use LRU cache for cross-request caching
59+
- `server-dedup-props` - Avoid duplicate serialization in RSC props
60+
- `server-serialization` - Minimize data passed to client components
61+
- `server-parallel-fetching` - Restructure components to parallelize fetches
62+
- `server-after-nonblocking` - Use after() for non-blocking operations
63+
64+
### 4. Client-Side Data Fetching (MEDIUM-HIGH)
65+
66+
- `client-swr-dedup` - Use SWR for automatic request deduplication
67+
- `client-event-listeners` - Deduplicate global event listeners
68+
- `client-passive-event-listeners` - Use passive listeners for scroll
69+
- `client-localstorage-schema` - Version and minimize localStorage data
70+
71+
### 5. Re-render Optimization (MEDIUM)
72+
73+
- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
74+
- `rerender-memo` - Extract expensive work into memoized components
75+
- `rerender-memo-with-default-value` - Hoist default non-primitive props
76+
- `rerender-dependencies` - Use primitive dependencies in effects
77+
- `rerender-derived-state` - Subscribe to derived booleans, not raw values
78+
- `rerender-derived-state-no-effect` - Derive state during render, not effects
79+
- `rerender-functional-setstate` - Use functional setState for stable callbacks
80+
- `rerender-lazy-state-init` - Pass function to useState for expensive values
81+
- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives
82+
- `rerender-move-effect-to-event` - Put interaction logic in event handlers
83+
- `rerender-transitions` - Use startTransition for non-urgent updates
84+
- `rerender-use-ref-transient-values` - Use refs for transient frequent values
85+
86+
### 6. Rendering Performance (MEDIUM)
87+
88+
- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
89+
- `rendering-content-visibility` - Use content-visibility for long lists
90+
- `rendering-hoist-jsx` - Extract static JSX outside components
91+
- `rendering-svg-precision` - Reduce SVG coordinate precision
92+
- `rendering-hydration-no-flicker` - Use inline script for client-only data
93+
- `rendering-hydration-suppress-warning` - Suppress expected mismatches
94+
- `rendering-activity` - Use Activity component for show/hide
95+
- `rendering-conditional-render` - Use ternary, not && for conditionals
96+
- `rendering-usetransition-loading` - Prefer useTransition for loading state
97+
98+
### 7. JavaScript Performance (LOW-MEDIUM)
99+
100+
- `js-batch-dom-css` - Group CSS changes via classes or cssText
101+
- `js-index-maps` - Build Map for repeated lookups
102+
- `js-cache-property-access` - Cache object properties in loops
103+
- `js-cache-function-results` - Cache function results in module-level Map
104+
- `js-cache-storage` - Cache localStorage/sessionStorage reads
105+
- `js-combine-iterations` - Combine multiple filter/map into one loop
106+
- `js-length-check-first` - Check array length before expensive comparison
107+
- `js-early-exit` - Return early from functions
108+
- `js-hoist-regexp` - Hoist RegExp creation outside loops
109+
- `js-min-max-loop` - Use loop for min/max instead of sort
110+
- `js-set-map-lookups` - Use Set/Map for O(1) lookups
111+
- `js-tosorted-immutable` - Use toSorted() for immutability
112+
113+
### 8. Advanced Patterns (LOW)
114+
115+
- `advanced-event-handler-refs` - Store event handlers in refs
116+
- `advanced-init-once` - Initialize app once per app load
117+
- `advanced-use-latest` - useLatest for stable callback refs
118+
119+
## How to Use
120+
121+
Read individual rule files for detailed explanations and code examples:
122+
123+
```
124+
rules/async-parallel.md
125+
rules/bundle-barrel-imports.md
126+
```
127+
128+
Each rule file contains:
129+
- Brief explanation of why it matters
130+
- Incorrect code example with explanation
131+
- Correct code example with explanation
132+
- Additional context and references
133+
134+
## Full Compiled Document
135+
136+
For the complete guide with all rules expanded: `AGENTS.md`
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: Store Event Handlers in Refs
3+
impact: LOW
4+
impactDescription: stable subscriptions
5+
tags: advanced, hooks, refs, event-handlers, optimization
6+
---
7+
8+
## Store Event Handlers in Refs
9+
10+
Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
11+
12+
**Incorrect (re-subscribes on every render):**
13+
14+
```tsx
15+
function useWindowEvent(event: string, handler: (e) => void) {
16+
useEffect(() => {
17+
window.addEventListener(event, handler)
18+
return () => window.removeEventListener(event, handler)
19+
}, [event, handler])
20+
}
21+
```
22+
23+
**Correct (stable subscription):**
24+
25+
```tsx
26+
function useWindowEvent(event: string, handler: (e) => void) {
27+
const handlerRef = useRef(handler)
28+
useEffect(() => {
29+
handlerRef.current = handler
30+
}, [handler])
31+
32+
useEffect(() => {
33+
const listener = (e) => handlerRef.current(e)
34+
window.addEventListener(event, listener)
35+
return () => window.removeEventListener(event, listener)
36+
}, [event])
37+
}
38+
```
39+
40+
**Alternative: use `useEffectEvent` if you're on latest React:**
41+
42+
```tsx
43+
import { useEffectEvent } from 'react'
44+
45+
function useWindowEvent(event: string, handler: (e) => void) {
46+
const onEvent = useEffectEvent(handler)
47+
48+
useEffect(() => {
49+
window.addEventListener(event, onEvent)
50+
return () => window.removeEventListener(event, onEvent)
51+
}, [event])
52+
}
53+
```
54+
55+
`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: Initialize App Once, Not Per Mount
3+
impact: LOW-MEDIUM
4+
impactDescription: avoids duplicate init in development
5+
tags: initialization, useEffect, app-startup, side-effects
6+
---
7+
8+
## Initialize App Once, Not Per Mount
9+
10+
Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
11+
12+
**Incorrect (runs twice in dev, re-runs on remount):**
13+
14+
```tsx
15+
function Comp() {
16+
useEffect(() => {
17+
loadFromStorage()
18+
checkAuthToken()
19+
}, [])
20+
21+
// ...
22+
}
23+
```
24+
25+
**Correct (once per app load):**
26+
27+
```tsx
28+
let didInit = false
29+
30+
function Comp() {
31+
useEffect(() => {
32+
if (didInit) return
33+
didInit = true
34+
loadFromStorage()
35+
checkAuthToken()
36+
}, [])
37+
38+
// ...
39+
}
40+
```
41+
42+
Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: useEffectEvent for Stable Callback Refs
3+
impact: LOW
4+
impactDescription: prevents effect re-runs
5+
tags: advanced, hooks, useEffectEvent, refs, optimization
6+
---
7+
8+
## useEffectEvent for Stable Callback Refs
9+
10+
Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
11+
12+
**Incorrect (effect re-runs on every callback change):**
13+
14+
```tsx
15+
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
16+
const [query, setQuery] = useState('')
17+
18+
useEffect(() => {
19+
const timeout = setTimeout(() => onSearch(query), 300)
20+
return () => clearTimeout(timeout)
21+
}, [query, onSearch])
22+
}
23+
```
24+
25+
**Correct (using React's useEffectEvent):**
26+
27+
```tsx
28+
import { useEffectEvent } from 'react';
29+
30+
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
31+
const [query, setQuery] = useState('')
32+
const onSearchEvent = useEffectEvent(onSearch)
33+
34+
useEffect(() => {
35+
const timeout = setTimeout(() => onSearchEvent(query), 300)
36+
return () => clearTimeout(timeout)
37+
}, [query])
38+
}
39+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: Prevent Waterfall Chains in API Routes
3+
impact: CRITICAL
4+
impactDescription: 2-10× improvement
5+
tags: api-routes, server-actions, waterfalls, parallelization
6+
---
7+
8+
## Prevent Waterfall Chains in API Routes
9+
10+
In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.
11+
12+
**Incorrect (config waits for auth, data waits for both):**
13+
14+
```typescript
15+
export async function GET(request: Request) {
16+
const session = await auth()
17+
const config = await fetchConfig()
18+
const data = await fetchData(session.user.id)
19+
return Response.json({ data, config })
20+
}
21+
```
22+
23+
**Correct (auth and config start immediately):**
24+
25+
```typescript
26+
export async function GET(request: Request) {
27+
const sessionPromise = auth()
28+
const configPromise = fetchConfig()
29+
const session = await sessionPromise
30+
const [config, data] = await Promise.all([
31+
configPromise,
32+
fetchData(session.user.id)
33+
])
34+
return Response.json({ data, config })
35+
}
36+
```
37+
38+
For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: Defer Await Until Needed
3+
impact: HIGH
4+
impactDescription: avoids blocking unused code paths
5+
tags: async, await, conditional, optimization
6+
---
7+
8+
## Defer Await Until Needed
9+
10+
Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.
11+
12+
**Incorrect (blocks both branches):**
13+
14+
```typescript
15+
async function handleRequest(userId: string, skipProcessing: boolean) {
16+
const userData = await fetchUserData(userId)
17+
18+
if (skipProcessing) {
19+
// Returns immediately but still waited for userData
20+
return { skipped: true }
21+
}
22+
23+
// Only this branch uses userData
24+
return processUserData(userData)
25+
}
26+
```
27+
28+
**Correct (only blocks when needed):**
29+
30+
```typescript
31+
async function handleRequest(userId: string, skipProcessing: boolean) {
32+
if (skipProcessing) {
33+
// Returns immediately without waiting
34+
return { skipped: true }
35+
}
36+
37+
// Fetch only when needed
38+
const userData = await fetchUserData(userId)
39+
return processUserData(userData)
40+
}
41+
```
42+
43+
**Another example (early return optimization):**
44+
45+
```typescript
46+
// Incorrect: always fetches permissions
47+
async function updateResource(resourceId: string, userId: string) {
48+
const permissions = await fetchPermissions(userId)
49+
const resource = await getResource(resourceId)
50+
51+
if (!resource) {
52+
return { error: 'Not found' }
53+
}
54+
55+
if (!permissions.canEdit) {
56+
return { error: 'Forbidden' }
57+
}
58+
59+
return await updateResourceData(resource, permissions)
60+
}
61+
62+
// Correct: fetches only when needed
63+
async function updateResource(resourceId: string, userId: string) {
64+
const resource = await getResource(resourceId)
65+
66+
if (!resource) {
67+
return { error: 'Not found' }
68+
}
69+
70+
const permissions = await fetchPermissions(userId)
71+
72+
if (!permissions.canEdit) {
73+
return { error: 'Forbidden' }
74+
}
75+
76+
return await updateResourceData(resource, permissions)
77+
}
78+
```
79+
80+
This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.

0 commit comments

Comments
 (0)