@minhduydev/mdpi 0.4.1 → 0.5.0
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 +1 -1
- package/dist/template/.pi/VERSION +1 -1
- package/dist/template/.pi/extensions/templates-injector.ts +35 -7
- package/dist/template/.pi/prompts/INDEX.md +3 -9
- package/dist/template/.pi/skills/INDEX.md +39 -8
- package/dist/template/.pi/skills/dcp-hygiene/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-advanced.md +88 -15
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-core.md +148 -13
- package/dist/template/.pi/skills/frontend-design/references/shadcn/setup.md +127 -20
- package/dist/template/.pi/skills/nextjs-app-router/SKILL.md +334 -0
- package/dist/template/.pi/skills/nextjs-cache/SKILL.md +262 -0
- package/dist/template/.pi/skills/react-best-practices/SKILL.md +79 -1
- package/dist/template/.pi/skills/react-compiler/SKILL.md +237 -0
- package/dist/template/.pi/skills/react-hook-form/SKILL.md +374 -0
- package/dist/template/.pi/skills/react-server-actions/SKILL.md +299 -0
- package/dist/template/.pi/skills/shadcn-ui/SKILL.md +404 -0
- package/dist/template/.pi/skills/tanstack-query/SKILL.md +330 -0
- package/dist/template/.pi/skills/v0/SKILL.md +264 -0
- package/dist/template/.pi/skills/zustand/SKILL.md +333 -0
- package/package.json +1 -1
- package/dist/template/.pi/prompts/loop-check.md +0 -87
- package/dist/template/.pi/prompts/loop-init.md +0 -157
- package/dist/template/.pi/prompts/loop-review.md +0 -90
- package/dist/template/.pi/skills/loop-audit/SKILL.md +0 -141
- package/dist/template/.pi/skills/loop-cost/SKILL.md +0 -130
- package/dist/template/.pi/skills/loop-engineering/SKILL.md +0 -175
- package/dist/template/.pi/templates/loop-github-action.yml +0 -162
- package/dist/template/.pi/templates/loop-orchestrator.sh +0 -514
- package/dist/template/.pi/templates/loop-orchestrator.test.ts +0 -332
- package/dist/template/.pi/templates/loop-orchestrator.ts +0 -936
- package/dist/template/.pi/templates/loop-state.json +0 -24
- package/dist/template/.pi/templates/loop-state.md +0 -98
- package/dist/template/.pi/templates/loop-vision.md +0 -110
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-cache
|
|
3
|
+
description: Use when working with Next.js 16 caching — `use cache` directive, cacheLife, cacheTag, revalidation, migration from v15. MUST load before implementing any caching strategy.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js Cache System (Next.js 16)
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Adding `use cache` to functions or components
|
|
11
|
+
- Configuring cache lifetimes with `cacheLife()`
|
|
12
|
+
- Tagging cache entries for targeted revalidation
|
|
13
|
+
- Migrating from Next.js 15's implicit cache (force-dynamic, fetch cache)
|
|
14
|
+
- Calling `revalidateTag()` / `revalidatePath()` after mutations
|
|
15
|
+
- Using `connection()` for database-aware caching
|
|
16
|
+
|
|
17
|
+
## When NOT to Use
|
|
18
|
+
|
|
19
|
+
- Pages Router projects (no `use cache` support)
|
|
20
|
+
- Next.js 14 or earlier (different cache model)
|
|
21
|
+
- Purely client-side caching (use TanStack Query or SWR)
|
|
22
|
+
|
|
23
|
+
## Core Pattern: `use cache`
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// app/lib/data.ts
|
|
27
|
+
import { cacheTag } from 'next/cache'
|
|
28
|
+
import { db } from '@/lib/db'
|
|
29
|
+
|
|
30
|
+
export async function getPosts() {
|
|
31
|
+
'use cache'
|
|
32
|
+
cacheTag('posts') // Tag for later revalidation
|
|
33
|
+
|
|
34
|
+
const posts = await db.post.findMany({
|
|
35
|
+
include: { author: true },
|
|
36
|
+
orderBy: { createdAt: 'desc' },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return posts
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// app/posts/page.tsx
|
|
45
|
+
import { getPosts } from '@/lib/data'
|
|
46
|
+
|
|
47
|
+
export default async function PostsPage() {
|
|
48
|
+
const posts = await getPosts() // Cached until revalidated or expired
|
|
49
|
+
return <PostsList posts={posts} />
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## cacheLife — Set TTL
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { cacheLife } from 'next/cache'
|
|
57
|
+
|
|
58
|
+
export async function getPopularPosts() {
|
|
59
|
+
'use cache'
|
|
60
|
+
cacheLife('hours') // Revalidate every hour
|
|
61
|
+
cacheTag('popular-posts')
|
|
62
|
+
|
|
63
|
+
return db.post.findMany({ where: { views: { gte: 1000 } } })
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Available `cacheLife` values:
|
|
68
|
+
|
|
69
|
+
| Profile | Duration | Use Case |
|
|
70
|
+
|---------|----------|----------|
|
|
71
|
+
| `'seconds'` | ~1 second | Real-time but deduped |
|
|
72
|
+
| `'minutes'` | ~5 minutes | Frequently changing data |
|
|
73
|
+
| `'hours'` | ~1 hour | Dashboard stats, user profiles |
|
|
74
|
+
| `'days'` | ~1 day | Blog content, static pages |
|
|
75
|
+
| `'weeks'` | ~1 week | Changelogs, documentation |
|
|
76
|
+
| `'max'` | Unlimited | Immutable data (never revalidates automatically) |
|
|
77
|
+
|
|
78
|
+
Custom profiles via `next.config.ts`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// next.config.ts
|
|
82
|
+
import type { NextConfig } from 'next'
|
|
83
|
+
|
|
84
|
+
const config: NextConfig = {
|
|
85
|
+
cacheLife: {
|
|
86
|
+
frequent: {
|
|
87
|
+
stale: 60, // seconds before background revalidate
|
|
88
|
+
revalidate: 300, // seconds before full re-fetch
|
|
89
|
+
expire: 3600, // seconds before purge
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## cacheTag — Target Revalidation
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// Tag multiple related caches
|
|
99
|
+
export async function getPost(id: string) {
|
|
100
|
+
'use cache'
|
|
101
|
+
cacheTag(`post-${id}`, 'posts') // Individual + list tag
|
|
102
|
+
|
|
103
|
+
return db.post.findUnique({ where: { id } })
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// app/actions.ts — revalidate after mutation
|
|
109
|
+
'use server'
|
|
110
|
+
|
|
111
|
+
import { revalidateTag, revalidatePath } from 'next/cache'
|
|
112
|
+
|
|
113
|
+
export async function deletePost(id: string) {
|
|
114
|
+
await db.post.delete({ where: { id } })
|
|
115
|
+
|
|
116
|
+
revalidateTag(`post-${id}`) // Revalidate specific post
|
|
117
|
+
revalidateTag('posts') // Revalidate all post lists
|
|
118
|
+
revalidatePath('/posts') // Also revalidate the URL path
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## connection() — Database-Driven Cache
|
|
123
|
+
|
|
124
|
+
`connection()` makes the cache aware of your database connection, speeding up purge:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { connection } from 'next/cache'
|
|
128
|
+
|
|
129
|
+
export async function getPost(id: string) {
|
|
130
|
+
'use cache'
|
|
131
|
+
cacheTag(`post-${id}`)
|
|
132
|
+
connection() // Invalidate when DB connection changes (e.g., deploy)
|
|
133
|
+
|
|
134
|
+
return db.post.findUnique({ where: { id } })
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Call `connection()` at any point in the cached function. Multiple calls deduplicate.
|
|
139
|
+
|
|
140
|
+
## Cacheable vs Non-Cacheable
|
|
141
|
+
|
|
142
|
+
**Can be cached**: Database queries, filesystem reads, fetch to stable APIs, computed values, Component output.
|
|
143
|
+
|
|
144
|
+
**Cannot be cached**: Request objects (`cookies()`, `headers()`), mutable state, random values, real-time data.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
export async function getUserData() {
|
|
148
|
+
const session = await auth() // ❌ Uses cookies — cannot cache
|
|
149
|
+
|
|
150
|
+
const user = await db.user.findUnique({
|
|
151
|
+
where: { id: session.userId }
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return user
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Solution**: Split the function — cache only the data part:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
export async function getUserData() {
|
|
162
|
+
const session = await auth() // Not cached
|
|
163
|
+
|
|
164
|
+
return getUser(session.userId) // Cached
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function getUser(id: string) {
|
|
168
|
+
'use cache'
|
|
169
|
+
cacheTag(`user-${id}`)
|
|
170
|
+
return db.user.findUnique({ where: { id } })
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Migration from Next.js 15
|
|
175
|
+
|
|
176
|
+
### Before (v15 implicit caching):
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// Next.js 15
|
|
180
|
+
export const dynamic = 'force-dynamic' // Opt out of caching
|
|
181
|
+
export const revalidate = 3600 // ISR interval
|
|
182
|
+
|
|
183
|
+
// fetch caching
|
|
184
|
+
const data = await fetch(url, { cache: 'no-store' })
|
|
185
|
+
const data = await fetch(url, { next: { revalidate: 60 } })
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### After (v16 explicit caching):
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// Next.js 16 — everything is dynamic by default
|
|
192
|
+
// To cache, use `use cache`:
|
|
193
|
+
export async function getPage() {
|
|
194
|
+
'use cache'
|
|
195
|
+
cacheLife('hours')
|
|
196
|
+
return db.page.findMany()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// No more fetch cache options — use cache instead:
|
|
200
|
+
const data = await fetch(url) // Always fresh, unless wrapped in use cache
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Quick Migration Table
|
|
204
|
+
|
|
205
|
+
| v15 Pattern | v16 Equivalent |
|
|
206
|
+
|------------|----------------|
|
|
207
|
+
| `fetch(url, { cache: 'no-store' })` | `fetch(url)` — no cache (default) |
|
|
208
|
+
| `fetch(url, { next: { revalidate: 60 } })` | Wrap in `use cache` + `cacheLife` |
|
|
209
|
+
| `export const dynamic = 'force-dynamic'` | Remove — dynamic is default |
|
|
210
|
+
| `export const revalidate = 3600` | `'use cache'` + `cacheLife('hours')` |
|
|
211
|
+
| `revalidatePath('/posts')` | Same API — still works |
|
|
212
|
+
| `revalidateTag('posts')` | Same API — still works with `cacheTag` |
|
|
213
|
+
| `unstable_cache(fn, ['key'])` | `'use cache'` + `cacheTag('key')` |
|
|
214
|
+
|
|
215
|
+
## Revalidation vs Expiration
|
|
216
|
+
|
|
217
|
+
- **Revalidate** (`revalidateTag`/`revalidatePath`): Immediate purge and re-fetch on next request
|
|
218
|
+
- **Expire** (`cacheLife` TTL): Background revalidate, stale data served until fresh data ready
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// Mutation pattern — revalidate affected caches
|
|
222
|
+
export async function updatePost(id: string, data: PostInput) {
|
|
223
|
+
await db.post.update({ where: { id }, data })
|
|
224
|
+
|
|
225
|
+
revalidateTag(`post-${id}`) // Immediate purge
|
|
226
|
+
// cacheLife handles TTL-based refresh for other entries
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Concurrent Mutations Safety
|
|
231
|
+
|
|
232
|
+
`use cache` functions support deduplication — concurrent requests for the same data share one database call:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// Three components call getPosts() on same page
|
|
236
|
+
// → only ONE database query executes
|
|
237
|
+
<PostsList /> // calls getPosts()
|
|
238
|
+
<RecentPosts /> // calls getPosts() — deduped
|
|
239
|
+
<PopularPosts /> // calls getPosts() — deduped
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Common Pitfalls
|
|
243
|
+
|
|
244
|
+
| Pitfall | Fix |
|
|
245
|
+
|---------|-----|
|
|
246
|
+
| Using `cookies()` inside `'use cache'` | Split function: read cookies outside, pass data into cached function |
|
|
247
|
+
| Forgetting `cacheTag()` | Without tags, cache can only be purged by TTL or full flush |
|
|
248
|
+
| Using `revalidatePath` too broadly | Prefer `revalidateTag` — narrower scope, less CPU |
|
|
249
|
+
| Not invalidating after mutation | Every mutation must revalidate affected caches |
|
|
250
|
+
| `cacheLife` too short for write-heavy data | Use `cacheLife('seconds')` or skip caching for hot data |
|
|
251
|
+
| Caching user-specific data without user-scoping | Include `userId` in `cacheTag`: `cacheTag('user-${id}-posts')` |
|
|
252
|
+
| Assuming cache survives deployment | Add `connection()` to auto-invalidate on deploy |
|
|
253
|
+
|
|
254
|
+
## Verification
|
|
255
|
+
|
|
256
|
+
- [ ] `'use cache'` functions have `cacheTag()` for targeted revalidation
|
|
257
|
+
- [ ] Mutations call `revalidateTag()` or `revalidatePath()`
|
|
258
|
+
- [ ] No `cookies()` or `headers()` inside cached functions
|
|
259
|
+
- [ ] `cacheLife` appropriate for data freshness requirements
|
|
260
|
+
- [ ] `connection()` added for database queries that should invalidate on deploy
|
|
261
|
+
- [ ] v15 `fetch` cache options removed or migrated
|
|
262
|
+
- [ ] User-scoped data uses user-specific cache tags
|
|
@@ -7,11 +7,16 @@ description: MUST load when writing, reviewing, or refactoring React/Next.js cod
|
|
|
7
7
|
|
|
8
8
|
## When to Use
|
|
9
9
|
|
|
10
|
-
- Applying performance guidelines to React/Next.js components or pages.
|
|
10
|
+
- Applying performance guidelines to React 19 / Next.js 16 components or pages.
|
|
11
|
+
- Optimizing Server Components, data fetching, bundle size, and re-renders.
|
|
11
12
|
|
|
12
13
|
## When NOT to Use
|
|
13
14
|
|
|
14
15
|
- Non-React codebases or UI-free/backend-only changes.
|
|
16
|
+
- Server Actions and form patterns (use `react-server-actions` skill)
|
|
17
|
+
- App Router architecture (use `nextjs-app-router` skill)
|
|
18
|
+
- Next.js 16 caching (use `nextjs-cache` skill)
|
|
19
|
+
- State management (use `tanstack-query`, `zustand`, or `react-hook-form` skills)
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
## When to Apply
|
|
@@ -108,6 +113,79 @@ Reference these guidelines when:
|
|
|
108
113
|
- `advanced-event-handler-refs` - Store event handlers in refs
|
|
109
114
|
- `advanced-use-latest` - useLatest for stable callback refs
|
|
110
115
|
|
|
116
|
+
## React 19 Patterns
|
|
117
|
+
|
|
118
|
+
### Server Components: Default Data Flow
|
|
119
|
+
|
|
120
|
+
React 19 + Next.js App Router defaults to **Server Components**. Keep data fetching on the server:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// ✅ Server Component — fetch data where it lives
|
|
124
|
+
// app/posts/page.tsx
|
|
125
|
+
export default async function PostsPage() {
|
|
126
|
+
const posts = await db.post.findMany() // Direct DB access
|
|
127
|
+
const config = await fetchConfig() // Private API calls (no CORS)
|
|
128
|
+
|
|
129
|
+
return <PostsList posts={posts} config={config} />
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Only add `'use client'` at the interactive leaves — push it as deep as possible.
|
|
134
|
+
|
|
135
|
+
### New Hooks Performance Impact
|
|
136
|
+
|
|
137
|
+
| Hook | Use Case | Performance Note |
|
|
138
|
+
|------|----------|-----------------|
|
|
139
|
+
| `useOptimistic` | Instant UI feedback | Replaces manual `useState` + revert logic |
|
|
140
|
+
| `useActionState` | Form submissions | Replaces `useFormState` (deprecated) |
|
|
141
|
+
| `useFormStatus` | Pending states | Read from child, not form component |
|
|
142
|
+
| `use()` | Unwrap promises in render | Only in Client Components — Server Components use `await` |
|
|
143
|
+
|
|
144
|
+
### React Compiler Awareness
|
|
145
|
+
|
|
146
|
+
The React Compiler (stable, React 19+) auto-memoizes components and hooks. With compiler enabled:
|
|
147
|
+
|
|
148
|
+
- **Remove** manual `useMemo`, `useCallback`, `memo()` unless truly expensive
|
|
149
|
+
- **Keep** `useRef` (semantic, not memoization)
|
|
150
|
+
- **Keep** `useEffect` for synchronization (compiler doesn't touch effects)
|
|
151
|
+
- See `react-compiler` skill for full migration guide
|
|
152
|
+
|
|
153
|
+
### `use()` for Client Component Data
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
'use client'
|
|
157
|
+
|
|
158
|
+
import { use } from 'react'
|
|
159
|
+
|
|
160
|
+
function UserProfile({ userPromise }) {
|
|
161
|
+
const user = use(userPromise) // Unwrap promise in render
|
|
162
|
+
return <div>{user.name}</div>
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`use()` reads Promises and Context in render without a hook wrapper.
|
|
167
|
+
|
|
168
|
+
## Next.js 16 Caching
|
|
169
|
+
|
|
170
|
+
Next.js 16 reversed the v15 caching model — everything is dynamic by default. To cache:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Cached — uses `use cache` directive
|
|
174
|
+
export async function getPosts() {
|
|
175
|
+
'use cache'
|
|
176
|
+
cacheLife('hours')
|
|
177
|
+
cacheTag('posts')
|
|
178
|
+
return db.post.findMany()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Dynamic — no directive (default)
|
|
182
|
+
export async function getUserSession() {
|
|
183
|
+
return auth() // Always fresh
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Migration**: Remove `export const dynamic = 'force-dynamic'` (now default). Replace `export const revalidate = 3600` with `'use cache'` + `cacheLife`. See `nextjs-cache` skill for detailed migration.
|
|
188
|
+
|
|
111
189
|
## How to Use
|
|
112
190
|
|
|
113
191
|
Read individual rule files for detailed explanations and code examples:
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-compiler
|
|
3
|
+
description: Use when enabling, debugging, or optimizing with the React Compiler. Covers what it auto-memoizes, what it can't optimize, ESLint plugin, migration guide, and React DevTools debugging. MUST load before enabling the compiler or diagnosing memoization issues.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Compiler (React 19+)
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Enabling the React Compiler in a project
|
|
11
|
+
- Understanding what the compiler does and doesn't optimize
|
|
12
|
+
- Debugging why a component didn't get memoized
|
|
13
|
+
- Diagnosing unexpected re-renders in a compiler-enabled project
|
|
14
|
+
- Deciding between manual `useMemo`/`useCallback` vs letting the compiler handle it
|
|
15
|
+
|
|
16
|
+
## When NOT to Use
|
|
17
|
+
|
|
18
|
+
- Non-React projects
|
|
19
|
+
- Debugging runtime rendering issues unrelated to memoization
|
|
20
|
+
- Choosing component architecture (use `react-best-practices` or `deep-module-design`)
|
|
21
|
+
|
|
22
|
+
## What the React Compiler Does
|
|
23
|
+
|
|
24
|
+
The React Compiler **auto-memoizes** components and hooks at build time. It eliminates the need for manual `useMemo`, `useCallback`, and `memo()` in most cases.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// Before compiler — manual memoization
|
|
28
|
+
function ExpensiveList({ items }: { items: Item[] }) {
|
|
29
|
+
const filtered = useMemo(
|
|
30
|
+
() => items.filter(i => i.active),
|
|
31
|
+
[items]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const handleClick = useCallback((id: string) => {
|
|
35
|
+
onSelect(id)
|
|
36
|
+
}, [onSelect])
|
|
37
|
+
|
|
38
|
+
return filtered.map(item => (
|
|
39
|
+
<Item key={item.id} item={item} onClick={handleClick} />
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// With compiler — writes itself
|
|
44
|
+
function ExpensiveList({ items }: { items: Item[] }) {
|
|
45
|
+
const filtered = items.filter(i => i.active)
|
|
46
|
+
|
|
47
|
+
return filtered.map(item => (
|
|
48
|
+
<Item key={item.id} item={item} onClick={() => onSelect(item.id)} />
|
|
49
|
+
))
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What the Compiler CAN Optimize
|
|
54
|
+
|
|
55
|
+
| Pattern | How |
|
|
56
|
+
|---------|-----|
|
|
57
|
+
| Components | Auto-wraps with `memo()` equivalent |
|
|
58
|
+
| Props & dependencies | Auto-generates dependency arrays for `useMemo`, `useCallback`, `useEffect` |
|
|
59
|
+
| Inline functions | Auto-memoizes `() => {}` passed as props |
|
|
60
|
+
| Computed values | Auto-wraps expensive computations in `useMemo` |
|
|
61
|
+
| Context reads | Tracks granular context reads — only re-renders when specific field changes |
|
|
62
|
+
| useRef stability | Stabilizes `useRef` references |
|
|
63
|
+
|
|
64
|
+
## What the Compiler CANNOT Optimize
|
|
65
|
+
|
|
66
|
+
| Limitation | What to Do |
|
|
67
|
+
|-----------|------------|
|
|
68
|
+
| **Side effects in render** | Fix — compiler bails out on impure renders |
|
|
69
|
+
| **Mutating state directly** | Fix — use setState functions |
|
|
70
|
+
| **Dynamic `key` values from `Math.random()`** | Fix — use stable keys |
|
|
71
|
+
| **Third-party libraries that break Rules of React** | Wait for library updates |
|
|
72
|
+
| **Components with `ref` + mutating `ref.current` in render** | Move mutation to event handler or effect |
|
|
73
|
+
| **Deeply nested context that's read broadly** | Split context or use selectors (Zustand) |
|
|
74
|
+
| **Extremely large dependency arrays (100+ items)** | Restructure: split component or function |
|
|
75
|
+
| **Code using `eval()` or `new Function()`** | Remove dynamic code evaluation |
|
|
76
|
+
|
|
77
|
+
## Enabling the Compiler
|
|
78
|
+
|
|
79
|
+
### Next.js (15+)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install babel-plugin-react-compiler
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// next.config.ts
|
|
87
|
+
import type { NextConfig } from 'next'
|
|
88
|
+
|
|
89
|
+
const nextConfig: NextConfig = {
|
|
90
|
+
experimental: {
|
|
91
|
+
reactCompiler: true,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Vite (React)
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm install babel-plugin-react-compiler
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// vite.config.ts
|
|
104
|
+
import { defineConfig } from 'vite'
|
|
105
|
+
import react from '@vitejs/plugin-react'
|
|
106
|
+
|
|
107
|
+
export default defineConfig({
|
|
108
|
+
plugins: [
|
|
109
|
+
react({
|
|
110
|
+
babel: {
|
|
111
|
+
plugins: [['babel-plugin-react-compiler', { target: '19' }]],
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ESLint Plugin
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install eslint-plugin-react-compiler
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
// .eslintrc.json
|
|
126
|
+
{
|
|
127
|
+
"plugins": ["react-compiler"],
|
|
128
|
+
"rules": {
|
|
129
|
+
"react-compiler/react-compiler": "error"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The ESLint plugin detects **Rules of React violations** that would cause the compiler to bail out — catches issues at lint time before they become runtime problems.
|
|
135
|
+
|
|
136
|
+
## React DevTools Integration
|
|
137
|
+
|
|
138
|
+
With the compiler enabled, React DevTools shows:
|
|
139
|
+
|
|
140
|
+
- **"Memo ✨"** badge on auto-memoized components
|
|
141
|
+
- Components that **failed** to compile — shown with a warning badge
|
|
142
|
+
- Why it failed: hover the badge for explanation
|
|
143
|
+
|
|
144
|
+
Open React DevTools → Components tab → look for ✨ beside component names.
|
|
145
|
+
|
|
146
|
+
## Migration Guide
|
|
147
|
+
|
|
148
|
+
### Phase 1: ESLint First (No Compiler)
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm install eslint-plugin-react-compiler
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Enable only the ESLint rule. Fix all violations. This ensures your code follows Rules of React — a prerequisite for the compiler.
|
|
155
|
+
|
|
156
|
+
### Phase 2: Enable Compiler on CI
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// next.config.ts — enable on CI/staging first
|
|
160
|
+
reactCompiler: process.env.CI === 'true'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Run your test suite. Check for behavioral changes.
|
|
164
|
+
|
|
165
|
+
### Phase 3: Remove Manual Memoization
|
|
166
|
+
|
|
167
|
+
Once the compiler is stable in production:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// Remove these — compiler handles them:
|
|
171
|
+
// - useMemo for computed values
|
|
172
|
+
// - useCallback for event handlers
|
|
173
|
+
// - React.memo() wrapping
|
|
174
|
+
|
|
175
|
+
// Keep these — they serve semantic purposes:
|
|
176
|
+
// - useMemo for expensive computations the compiler can't prove
|
|
177
|
+
// - useRef for mutable values
|
|
178
|
+
// - useEffect for synchronization
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Phase 4: Full Production Enable
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
reactCompiler: true
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Gradual Adoption
|
|
188
|
+
|
|
189
|
+
Enable per-directory via compiler directive comments:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
// Opt-in specific files:
|
|
193
|
+
'use memo'
|
|
194
|
+
|
|
195
|
+
// Opt-out specific files:
|
|
196
|
+
'use no memo'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Use `'use memo'` at the top of a file to enable the compiler for that file only, even if the project-level config is off.
|
|
200
|
+
|
|
201
|
+
## When to Keep Manual Memoization
|
|
202
|
+
|
|
203
|
+
Even with the compiler, keep manual memoization for:
|
|
204
|
+
|
|
205
|
+
- **Truly expensive computations**: The compiler is conservative — if you know something is heavy, `useMemo` makes it explicit
|
|
206
|
+
- **Custom hooks with object returns**: `useMemo` on the return value ensures referential stability
|
|
207
|
+
- **Third-party integration**: When passing objects to non-React libraries that do shallow comparisons
|
|
208
|
+
|
|
209
|
+
## Common Pitfalls
|
|
210
|
+
|
|
211
|
+
| Pitfall | Fix |
|
|
212
|
+
|---------|-----|
|
|
213
|
+
| Side effects during render (console.log, date, random) | Move to event handlers or effects |
|
|
214
|
+
| Mutating props or state in render | Use immutable updates |
|
|
215
|
+
| Assuming compiler fixes all performance | Compiler handles memoization only — waterfalls, bundle size, SSR still need attention |
|
|
216
|
+
| Leaving manual memoization everywhere | Remove redundant `useMemo`/`useCallback` — they add noise with no benefit |
|
|
217
|
+
| Not running ESLint plugin before enabling | Run ESLint first and fix all violations — this is the #1 source of compiler bailouts |
|
|
218
|
+
| `useMemo` with empty dependency for object identity | Compiler handles this — remove unless it's truly expensive |
|
|
219
|
+
|
|
220
|
+
## Integration with Other Skills
|
|
221
|
+
|
|
222
|
+
| Skill | How Compiler Changes It |
|
|
223
|
+
|-------|------------------------|
|
|
224
|
+
| `react-best-practices` | `rerender-memo`, `rerender-functional-setstate` become unnecessary with compiler |
|
|
225
|
+
| `react-server-actions` | No change — Server Actions don't use compiler |
|
|
226
|
+
| `zustand` | Selective subscriptions still recommended — compiler doesn't replace selectors |
|
|
227
|
+
| `tanstack-query` | No change — data fetching patterns unchanged |
|
|
228
|
+
|
|
229
|
+
## Verification
|
|
230
|
+
|
|
231
|
+
- [ ] ESLint plugin enabled and passing with zero violations
|
|
232
|
+
- [ ] Compiler enabled in `next.config.ts` or `vite.config.ts`
|
|
233
|
+
- [ ] React DevTools shows ✨ on auto-memoized components
|
|
234
|
+
- [ ] No unexpected re-renders after enabling (profile with DevTools)
|
|
235
|
+
- [ ] Test suite passes with compiler enabled
|
|
236
|
+
- [ ] Manual `useMemo`/`useCallback` removed where compiler handles them
|
|
237
|
+
- [ ] No side effects in render (build warnings if any)
|