@kood/claude-code 0.3.12 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +30 -8
- package/package.json +4 -4
- package/templates/.claude/skills/korea-uiux-design/SKILL.md +444 -0
- package/templates/.claude/skills/korea-uiux-design/references/accessibility.md +298 -0
- package/templates/.claude/skills/korea-uiux-design/references/checklist.md +107 -0
- package/templates/.claude/skills/korea-uiux-design/references/color-system.md +156 -0
- package/templates/.claude/skills/korea-uiux-design/references/design-philosophy.md +25 -0
- package/templates/.claude/skills/korea-uiux-design/references/icon-guide.md +180 -0
- package/templates/.claude/skills/korea-uiux-design/references/micro-interactions.md +259 -0
- package/templates/.claude/skills/korea-uiux-design/references/responsive-patterns.md +115 -0
- package/templates/.claude/skills/korea-uiux-design/references/service-patterns.md +206 -0
- package/templates/.claude/skills/korea-uiux-design/references/state-patterns.md +320 -0
- package/templates/.claude/skills/korea-uiux-design/references/typography.md +70 -0
- package/templates/.claude/skills/nextjs-react-best-practices/AGENTS.md +663 -0
- package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +269 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +751 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +431 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-defer-await.md +80 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-dependencies.md +36 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-loader.md +44 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-parallel.md +28 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-conditional.md +31 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-lazy-routes.md +67 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-preload.md +50 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-event-listeners.md +74 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-tanstack-query.md +77 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-cache-storage.md +70 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-early-exit.md +50 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-index-maps.md +37 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-length-check-first.md +49 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-memo.md +44 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-transitions.md +40 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-cache-lru.md +41 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +67 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-parallel-fetching.md +60 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +38 -0
- package/templates/.claude/skills/vercel-react-best-practices/AGENTS.md +0 -2249
- package/templates/.claude/skills/vercel-react-best-practices/SKILL.md +0 -125
- package/templates/.claude/skills/vs-design-diverge/SKILL.md +0 -307
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/advanced-use-latest.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/async-api-routes.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/async-defer-await.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/async-dependencies.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/async-parallel.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/bundle-conditional.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/bundle-preload.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/client-event-listeners.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/client-swr-dedup.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-batch-dom-css.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-cache-function-results.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-cache-property-access.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-cache-storage.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-combine-iterations.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-early-exit.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-hoist-regexp.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-index-maps.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-length-check-first.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-min-max-loop.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-set-map-lookups.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-activity.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-conditional-render.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-content-visibility.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rendering-svg-precision.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-defer-reads.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-dependencies.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-derived-state.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-memo.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/rerender-transitions.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/server-after-nonblocking.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/server-cache-lru.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/server-cache-react.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/server-parallel-fetching.md +0 -0
- /package/templates/.claude/skills/{vercel-react-best-practices → nextjs-react-best-practices}/rules/server-serialization.md +0 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
# Next.js React Best Practices
|
|
2
|
+
|
|
3
|
+
**Version 1.0.0**
|
|
4
|
+
Vercel Engineering
|
|
5
|
+
January 2026
|
|
6
|
+
|
|
7
|
+
> **Note:**
|
|
8
|
+
> This document is mainly for agents and LLMs to follow when maintaining,
|
|
9
|
+
> generating, or refactoring React and Next.js codebases. Humans
|
|
10
|
+
> may also find it useful, but guidance here is optimized for automation
|
|
11
|
+
> and consistency by AI-assisted workflows.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<communication>
|
|
16
|
+
|
|
17
|
+
## User Communication Protocol
|
|
18
|
+
|
|
19
|
+
**CRITICAL: Always communicate with the user in Korean (한국어).**
|
|
20
|
+
|
|
21
|
+
This applies to:
|
|
22
|
+
- Questions and clarifications
|
|
23
|
+
- Progress updates and summaries
|
|
24
|
+
- Explaining decisions and trade-offs
|
|
25
|
+
- Reporting errors or blockers
|
|
26
|
+
|
|
27
|
+
Internal processing and code comments remain in English. Only user-facing messages must be in Korean.
|
|
28
|
+
|
|
29
|
+
</communication>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Abstract
|
|
34
|
+
|
|
35
|
+
Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<instructions>
|
|
40
|
+
|
|
41
|
+
## Document Usage Instructions
|
|
42
|
+
|
|
43
|
+
@rules/async-defer-await.md
|
|
44
|
+
@rules/async-parallel.md
|
|
45
|
+
@rules/async-dependencies.md
|
|
46
|
+
@rules/async-api-routes.md
|
|
47
|
+
@rules/async-suspense-boundaries.md
|
|
48
|
+
@rules/bundle-barrel-imports.md
|
|
49
|
+
@rules/bundle-conditional.md
|
|
50
|
+
@rules/bundle-defer-third-party.md
|
|
51
|
+
@rules/bundle-dynamic-imports.md
|
|
52
|
+
@rules/bundle-preload.md
|
|
53
|
+
@rules/server-cache-react.md
|
|
54
|
+
@rules/server-cache-lru.md
|
|
55
|
+
@rules/server-serialization.md
|
|
56
|
+
@rules/server-parallel-fetching.md
|
|
57
|
+
@rules/server-after-nonblocking.md
|
|
58
|
+
@rules/client-event-listeners.md
|
|
59
|
+
@rules/client-swr-dedup.md
|
|
60
|
+
@rules/rerender-defer-reads.md
|
|
61
|
+
@rules/rerender-memo.md
|
|
62
|
+
@rules/rerender-dependencies.md
|
|
63
|
+
@rules/rerender-derived-state.md
|
|
64
|
+
@rules/rerender-functional-setstate.md
|
|
65
|
+
@rules/rerender-lazy-state-init.md
|
|
66
|
+
@rules/rerender-transitions.md
|
|
67
|
+
@rules/rendering-animate-svg-wrapper.md
|
|
68
|
+
@rules/rendering-content-visibility.md
|
|
69
|
+
@rules/rendering-hoist-jsx.md
|
|
70
|
+
@rules/rendering-svg-precision.md
|
|
71
|
+
@rules/rendering-hydration-no-flicker.md
|
|
72
|
+
@rules/rendering-activity.md
|
|
73
|
+
@rules/rendering-conditional-render.md
|
|
74
|
+
@rules/js-batch-dom-css.md
|
|
75
|
+
@rules/js-index-maps.md
|
|
76
|
+
@rules/js-cache-property-access.md
|
|
77
|
+
@rules/js-cache-function-results.md
|
|
78
|
+
@rules/js-cache-storage.md
|
|
79
|
+
@rules/js-combine-iterations.md
|
|
80
|
+
@rules/js-length-check-first.md
|
|
81
|
+
@rules/js-early-exit.md
|
|
82
|
+
@rules/js-hoist-regexp.md
|
|
83
|
+
@rules/js-min-max-loop.md
|
|
84
|
+
@rules/js-set-map-lookups.md
|
|
85
|
+
@rules/js-tosorted-immutable.md
|
|
86
|
+
@rules/advanced-event-handler-refs.md
|
|
87
|
+
@rules/advanced-use-latest.md
|
|
88
|
+
|
|
89
|
+
</instructions>
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
<categories>
|
|
94
|
+
|
|
95
|
+
## Categories by Priority
|
|
96
|
+
|
|
97
|
+
| Priority | Category | Impact | Description |
|
|
98
|
+
|----------|----------|--------|-------------|
|
|
99
|
+
| 1 | Eliminating Waterfalls | **CRITICAL** | Convert sequential awaits to parallel. Biggest gains |
|
|
100
|
+
| 2 | Bundle Size Optimization | **CRITICAL** | Improve TTI and LCP. Faster initial loads |
|
|
101
|
+
| 3 | Server-Side Performance | HIGH | Eliminate server-side waterfalls, reduce response times |
|
|
102
|
+
| 4 | Client-Side Data Fetching | MEDIUM-HIGH | Automatic deduplication, efficient data fetching |
|
|
103
|
+
| 5 | Re-render Optimization | MEDIUM | Minimize unnecessary re-renders, improve UI responsiveness |
|
|
104
|
+
| 6 | Rendering Performance | MEDIUM | Optimize browser rendering work |
|
|
105
|
+
| 7 | JavaScript Performance | LOW-MEDIUM | Hot path micro-optimizations |
|
|
106
|
+
| 8 | Advanced Patterns | LOW | Advanced implementation patterns for specific cases |
|
|
107
|
+
|
|
108
|
+
</categories>
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
<critical_patterns>
|
|
113
|
+
|
|
114
|
+
## 1. Eliminating Waterfalls (CRITICAL)
|
|
115
|
+
|
|
116
|
+
Waterfalls are the #1 performance killer. Each sequential await adds full network latency.
|
|
117
|
+
|
|
118
|
+
### Parallel Execution
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// ❌ Sequential execution (3 round trips)
|
|
122
|
+
const user = await fetchUser()
|
|
123
|
+
const posts = await fetchPosts()
|
|
124
|
+
const comments = await fetchComments()
|
|
125
|
+
|
|
126
|
+
// ✅ Parallel execution (1 round trip)
|
|
127
|
+
const [user, posts, comments] = await Promise.all([
|
|
128
|
+
fetchUser(),
|
|
129
|
+
fetchPosts(),
|
|
130
|
+
fetchComments()
|
|
131
|
+
])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Dependency-Based Parallelization
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// ❌ profile waits for config unnecessarily
|
|
138
|
+
const [user, config] = await Promise.all([
|
|
139
|
+
fetchUser(),
|
|
140
|
+
fetchConfig()
|
|
141
|
+
])
|
|
142
|
+
const profile = await fetchProfile(user.id)
|
|
143
|
+
|
|
144
|
+
// ✅ config and profile run in parallel with better-all
|
|
145
|
+
import { all } from 'better-all'
|
|
146
|
+
|
|
147
|
+
const { user, config, profile } = await all({
|
|
148
|
+
async user() { return fetchUser() },
|
|
149
|
+
async config() { return fetchConfig() },
|
|
150
|
+
async profile() {
|
|
151
|
+
return fetchProfile((await this.$.user).id)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Suspense Boundaries
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// ❌ Entire layout blocked by data fetching
|
|
160
|
+
async function Page() {
|
|
161
|
+
const data = await fetchData()
|
|
162
|
+
return (
|
|
163
|
+
<div>
|
|
164
|
+
<Sidebar />
|
|
165
|
+
<Header />
|
|
166
|
+
<DataDisplay data={data} />
|
|
167
|
+
<Footer />
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ✅ Wrapper shows immediately, data streams in
|
|
173
|
+
function Page() {
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
<Sidebar />
|
|
177
|
+
<Header />
|
|
178
|
+
<Suspense fallback={<Skeleton />}>
|
|
179
|
+
<DataDisplay />
|
|
180
|
+
</Suspense>
|
|
181
|
+
<Footer />
|
|
182
|
+
</div>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function DataDisplay() {
|
|
187
|
+
const data = await fetchData()
|
|
188
|
+
return <div>{data.content}</div>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
</critical_patterns>
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
<bundle_optimization>
|
|
197
|
+
|
|
198
|
+
## 2. Bundle Size Optimization (CRITICAL)
|
|
199
|
+
|
|
200
|
+
### Avoid Barrel File Imports
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
// ❌ Import entire library (1583 modules, ~2.8s)
|
|
204
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
205
|
+
|
|
206
|
+
// ✅ Direct imports (3 modules only)
|
|
207
|
+
import Check from 'lucide-react/dist/esm/icons/check'
|
|
208
|
+
import X from 'lucide-react/dist/esm/icons/x'
|
|
209
|
+
import Menu from 'lucide-react/dist/esm/icons/menu'
|
|
210
|
+
|
|
211
|
+
// ✅ Next.js 13.5+ auto-optimization
|
|
212
|
+
// next.config.js
|
|
213
|
+
module.exports = {
|
|
214
|
+
experimental: {
|
|
215
|
+
optimizePackageImports: ['lucide-react', '@mui/material']
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Affected libraries: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`
|
|
221
|
+
|
|
222
|
+
### Dynamic Imports
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// ❌ Monaco bundled with main chunk (~300KB)
|
|
226
|
+
import { MonacoEditor } from './monaco-editor'
|
|
227
|
+
|
|
228
|
+
function CodePanel({ code }: { code: string }) {
|
|
229
|
+
return <MonacoEditor value={code} />
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ✅ Monaco loads on demand
|
|
233
|
+
import dynamic from 'next/dynamic'
|
|
234
|
+
|
|
235
|
+
const MonacoEditor = dynamic(
|
|
236
|
+
() => import('./monaco-editor').then(m => m.MonacoEditor),
|
|
237
|
+
{ ssr: false }
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
function CodePanel({ code }: { code: string }) {
|
|
241
|
+
return <MonacoEditor value={code} />
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
</bundle_optimization>
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
<server_performance>
|
|
250
|
+
|
|
251
|
+
## 3. Server-Side Performance (HIGH)
|
|
252
|
+
|
|
253
|
+
### React.cache() - Per-Request Deduplication
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { cache } from 'react'
|
|
257
|
+
|
|
258
|
+
export const getCurrentUser = cache(async () => {
|
|
259
|
+
const session = await auth()
|
|
260
|
+
if (!session?.user?.id) return null
|
|
261
|
+
return await db.user.findUnique({ where: { id: session.user.id } })
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Within single request, multiple calls execute query only once
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### LRU Cache - Cross-Request Caching
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { LRUCache } from 'lru-cache'
|
|
271
|
+
|
|
272
|
+
const cache = new LRUCache<string, any>({
|
|
273
|
+
max: 1000,
|
|
274
|
+
ttl: 5 * 60 * 1000 // 5 minutes
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
export async function getUser(id: string) {
|
|
278
|
+
const cached = cache.get(id)
|
|
279
|
+
if (cached) return cached
|
|
280
|
+
|
|
281
|
+
const user = await db.user.findUnique({ where: { id } })
|
|
282
|
+
cache.set(id, user)
|
|
283
|
+
return user
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Request 1: DB query, result cached
|
|
287
|
+
// Request 2: cache hit, no DB query
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Minimize Serialization at RSC Boundaries
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// ❌ Serializes all 50 fields
|
|
294
|
+
async function Page() {
|
|
295
|
+
const user = await fetchUser() // 50 fields
|
|
296
|
+
return <Profile user={user} />
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
'use client'
|
|
300
|
+
function Profile({ user }: { user: User }) {
|
|
301
|
+
return <div>{user.name}</div> // uses 1 field
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ✅ Serializes only 1 field
|
|
305
|
+
async function Page() {
|
|
306
|
+
const user = await fetchUser()
|
|
307
|
+
return <Profile name={user.name} />
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
'use client'
|
|
311
|
+
function Profile({ name }: { name: string }) {
|
|
312
|
+
return <div>{name}</div>
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### after() for Non-Blocking Operations
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import { after } from 'next/server'
|
|
320
|
+
|
|
321
|
+
export async function POST(request: Request) {
|
|
322
|
+
await updateDatabase(request)
|
|
323
|
+
|
|
324
|
+
// Log after response is sent
|
|
325
|
+
after(async () => {
|
|
326
|
+
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
|
327
|
+
logUserAction({ userAgent })
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
return new Response(JSON.stringify({ status: 'success' }), {
|
|
331
|
+
status: 200,
|
|
332
|
+
headers: { 'Content-Type': 'application/json' }
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
</server_performance>
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
<client_data_fetching>
|
|
342
|
+
|
|
343
|
+
## 4. Client-Side Data Fetching (MEDIUM-HIGH)
|
|
344
|
+
|
|
345
|
+
### SWR for Automatic Deduplication
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import useSWR from 'swr'
|
|
349
|
+
|
|
350
|
+
// ❌ No deduplication, each instance fetches
|
|
351
|
+
function UserList() {
|
|
352
|
+
const [users, setUsers] = useState([])
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
fetch('/api/users')
|
|
355
|
+
.then(r => r.json())
|
|
356
|
+
.then(setUsers)
|
|
357
|
+
}, [])
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ✅ Multiple instances share one request
|
|
361
|
+
function UserList() {
|
|
362
|
+
const { data: users } = useSWR('/api/users', fetcher)
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
</client_data_fetching>
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
<rerender_optimization>
|
|
371
|
+
|
|
372
|
+
## 5. Re-render Optimization (MEDIUM)
|
|
373
|
+
|
|
374
|
+
### Functional setState
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// ❌ items as dependency, recreated every time
|
|
378
|
+
function TodoList() {
|
|
379
|
+
const [items, setItems] = useState(initialItems)
|
|
380
|
+
|
|
381
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
382
|
+
setItems([...items, ...newItems])
|
|
383
|
+
}, [items]) // Recreated on items change
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ✅ Stable callback, never recreated
|
|
387
|
+
function TodoList() {
|
|
388
|
+
const [items, setItems] = useState(initialItems)
|
|
389
|
+
|
|
390
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
391
|
+
setItems(curr => [...curr, ...newItems])
|
|
392
|
+
}, []) // No dependencies, always uses latest state
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Lazy State Initialization
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
// ❌ Runs on every render
|
|
400
|
+
function FilteredList({ items }: { items: Item[] }) {
|
|
401
|
+
const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
|
|
402
|
+
const [query, setQuery] = useState('')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ✅ Runs only on initial render
|
|
406
|
+
function FilteredList({ items }: { items: Item[] }) {
|
|
407
|
+
const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
|
|
408
|
+
const [query, setQuery] = useState('')
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Subscribe to Derived State
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
// ❌ Re-renders on every pixel change
|
|
416
|
+
function Sidebar() {
|
|
417
|
+
const width = useWindowWidth() // continuous updates
|
|
418
|
+
const isMobile = width < 768
|
|
419
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ✅ Re-renders only when boolean changes
|
|
423
|
+
function Sidebar() {
|
|
424
|
+
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
425
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
</rerender_optimization>
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
<rendering_performance>
|
|
434
|
+
|
|
435
|
+
## 6. Rendering Performance (MEDIUM)
|
|
436
|
+
|
|
437
|
+
### Animate SVG Wrapper
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
// ❌ Animating SVG directly - no hardware acceleration
|
|
441
|
+
function LoadingSpinner() {
|
|
442
|
+
return (
|
|
443
|
+
<svg className="animate-spin" width="24" height="24">
|
|
444
|
+
<circle cx="12" cy="12" r="10" />
|
|
445
|
+
</svg>
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ✅ Animating wrapper div - hardware accelerated
|
|
450
|
+
function LoadingSpinner() {
|
|
451
|
+
return (
|
|
452
|
+
<div className="animate-spin">
|
|
453
|
+
<svg width="24" height="24">
|
|
454
|
+
<circle cx="12" cy="12" r="10" />
|
|
455
|
+
</svg>
|
|
456
|
+
</div>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### content-visibility
|
|
462
|
+
|
|
463
|
+
```css
|
|
464
|
+
/* Long list optimization */
|
|
465
|
+
.message-item {
|
|
466
|
+
content-visibility: auto;
|
|
467
|
+
contain-intrinsic-size: 0 80px;
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
function MessageList({ messages }: { messages: Message[] }) {
|
|
473
|
+
return (
|
|
474
|
+
<div className="overflow-y-auto h-screen">
|
|
475
|
+
{messages.map(msg => (
|
|
476
|
+
<div key={msg.id} className="message-item">
|
|
477
|
+
<Avatar user={msg.author} />
|
|
478
|
+
<div>{msg.content}</div>
|
|
479
|
+
</div>
|
|
480
|
+
))}
|
|
481
|
+
</div>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render)
|
|
487
|
+
|
|
488
|
+
### Prevent Hydration Mismatch
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
// ❌ SSR failure
|
|
492
|
+
function ThemeWrapper({ children }: { children: ReactNode }) {
|
|
493
|
+
const theme = localStorage.getItem('theme') || 'light' // localStorage undefined on server
|
|
494
|
+
return <div className={theme}>{children}</div>
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ❌ Flickering
|
|
498
|
+
function ThemeWrapper({ children }: { children: ReactNode }) {
|
|
499
|
+
const [theme, setTheme] = useState('light')
|
|
500
|
+
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
const stored = localStorage.getItem('theme')
|
|
503
|
+
if (stored) setTheme(stored) // Runs after hydration → flicker
|
|
504
|
+
}, [])
|
|
505
|
+
|
|
506
|
+
return <div className={theme}>{children}</div>
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ✅ No flicker, no hydration mismatch
|
|
510
|
+
function ThemeWrapper({ children }: { children: ReactNode }) {
|
|
511
|
+
return (
|
|
512
|
+
<>
|
|
513
|
+
<div id="theme-wrapper">{children}</div>
|
|
514
|
+
<script
|
|
515
|
+
dangerouslySetInnerHTML={{
|
|
516
|
+
__html: `
|
|
517
|
+
(function() {
|
|
518
|
+
try {
|
|
519
|
+
var theme = localStorage.getItem('theme') || 'light';
|
|
520
|
+
var el = document.getElementById('theme-wrapper');
|
|
521
|
+
if (el) el.className = theme;
|
|
522
|
+
} catch (e) {}
|
|
523
|
+
})();
|
|
524
|
+
`,
|
|
525
|
+
}}
|
|
526
|
+
/>
|
|
527
|
+
</>
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
</rendering_performance>
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
<javascript_performance>
|
|
537
|
+
|
|
538
|
+
## 7. JavaScript Performance (LOW-MEDIUM)
|
|
539
|
+
|
|
540
|
+
### Build Index Maps for Repeated Lookups
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
// ❌ O(n) per lookup
|
|
544
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
545
|
+
return orders.map(order => ({
|
|
546
|
+
...order,
|
|
547
|
+
user: users.find(u => u.id === order.userId)
|
|
548
|
+
}))
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ✅ O(1) per lookup
|
|
552
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
553
|
+
const userById = new Map(users.map(u => [u.id, u]))
|
|
554
|
+
return orders.map(order => ({
|
|
555
|
+
...order,
|
|
556
|
+
user: userById.get(order.userId)
|
|
557
|
+
}))
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
1000 orders × 1000 users: 1M ops → 2K ops
|
|
562
|
+
|
|
563
|
+
### Early Length Check for Array Comparisons
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// ❌ Always runs expensive comparison
|
|
567
|
+
function hasChanges(current: string[], original: string[]) {
|
|
568
|
+
return current.sort().join() !== original.sort().join()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ✅ O(1) length check first
|
|
572
|
+
function hasChanges(current: string[], original: string[]) {
|
|
573
|
+
if (current.length !== original.length) return true
|
|
574
|
+
|
|
575
|
+
const currentSorted = current.toSorted()
|
|
576
|
+
const originalSorted = original.toSorted()
|
|
577
|
+
for (let i = 0; i < currentSorted.length; i++) {
|
|
578
|
+
if (currentSorted[i] !== originalSorted[i]) return true
|
|
579
|
+
}
|
|
580
|
+
return false
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Use toSorted() for Immutability
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// ❌ Mutates original array
|
|
588
|
+
function UserList({ users }: { users: User[] }) {
|
|
589
|
+
const sorted = useMemo(
|
|
590
|
+
() => users.sort((a, b) => a.name.localeCompare(b.name)),
|
|
591
|
+
[users]
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ✅ Creates new array
|
|
596
|
+
function UserList({ users }: { users: User[] }) {
|
|
597
|
+
const sorted = useMemo(
|
|
598
|
+
() => users.toSorted((a, b) => a.name.localeCompare(b.name)),
|
|
599
|
+
[users]
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
</javascript_performance>
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
<advanced_patterns>
|
|
609
|
+
|
|
610
|
+
## 8. Advanced Patterns (LOW)
|
|
611
|
+
|
|
612
|
+
### useLatest for Stable Callback Refs
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
function useLatest<T>(value: T) {
|
|
616
|
+
const ref = useRef(value)
|
|
617
|
+
useEffect(() => {
|
|
618
|
+
ref.current = value
|
|
619
|
+
}, [value])
|
|
620
|
+
return ref
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
```tsx
|
|
625
|
+
// ❌ Effect re-runs on every callback change
|
|
626
|
+
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
|
|
627
|
+
const [query, setQuery] = useState('')
|
|
628
|
+
|
|
629
|
+
useEffect(() => {
|
|
630
|
+
const timeout = setTimeout(() => onSearch(query), 300)
|
|
631
|
+
return () => clearTimeout(timeout)
|
|
632
|
+
}, [query, onSearch])
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ✅ Stable effect, fresh callback
|
|
636
|
+
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
|
|
637
|
+
const [query, setQuery] = useState('')
|
|
638
|
+
const onSearchRef = useLatest(onSearch)
|
|
639
|
+
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
const timeout = setTimeout(() => onSearchRef.current(query), 300)
|
|
642
|
+
return () => clearTimeout(timeout)
|
|
643
|
+
}, [query])
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
</advanced_patterns>
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
<references>
|
|
652
|
+
|
|
653
|
+
## References
|
|
654
|
+
|
|
655
|
+
1. [React](https://react.dev)
|
|
656
|
+
2. [Next.js](https://nextjs.org)
|
|
657
|
+
3. [SWR](https://swr.vercel.app)
|
|
658
|
+
4. [better-all](https://github.com/shuding/better-all)
|
|
659
|
+
5. [node-lru-cache](https://github.com/isaacs/node-lru-cache)
|
|
660
|
+
6. [Next.js Package Import Optimization](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
|
|
661
|
+
7. [Vercel Dashboard Twice as Fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
|
|
662
|
+
|
|
663
|
+
</references>
|