@kood/claude-code 0.3.12 → 0.3.14
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/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/{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,431 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-start-react-best-practices
|
|
3
|
+
description: TanStack Start and React performance optimization guide. Ensures optimal performance patterns when writing, reviewing, or refactoring TanStack Start pages and React components. Triggers on tasks involving React components, TanStack Router routes, data fetching, bundle optimization, or performance improvements.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: vercel
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
adapted_for: tanstack-start
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# TanStack Start React Best Practices
|
|
12
|
+
|
|
13
|
+
Performance optimization guide for React and TanStack Start applications. Contains 39 rules across 7 categories, prioritized by impact to guide automated refactoring and code generation.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<communication>
|
|
18
|
+
|
|
19
|
+
## User Communication
|
|
20
|
+
|
|
21
|
+
**IMPORTANT: Always communicate with the user in Korean (한국어), even though this document is in English.**
|
|
22
|
+
|
|
23
|
+
When:
|
|
24
|
+
- Asking questions
|
|
25
|
+
- Providing summaries
|
|
26
|
+
- Explaining decisions
|
|
27
|
+
- Reporting progress
|
|
28
|
+
|
|
29
|
+
Use Korean for all user-facing communication while applying these English guidelines internally.
|
|
30
|
+
|
|
31
|
+
</communication>
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<when_to_use>
|
|
36
|
+
|
|
37
|
+
## When to Apply
|
|
38
|
+
|
|
39
|
+
| Situation | Description |
|
|
40
|
+
|-----------|-------------|
|
|
41
|
+
| **Writing Components** | Creating new React components or TanStack Start routes |
|
|
42
|
+
| **Data Fetching** | Implementing client-side or server-side data fetching |
|
|
43
|
+
| **Code Review** | Reviewing code for performance issues |
|
|
44
|
+
| **Refactoring** | Improving existing React/TanStack Start code |
|
|
45
|
+
| **Optimization** | Optimizing bundle size or load times |
|
|
46
|
+
|
|
47
|
+
</when_to_use>
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
<categories>
|
|
52
|
+
|
|
53
|
+
## Rule Categories by Priority
|
|
54
|
+
|
|
55
|
+
| Priority | Category | Impact | Prefix |
|
|
56
|
+
|----------|----------|--------|--------|
|
|
57
|
+
| 1 | Eliminating Waterfalls | **CRITICAL** | `async-` |
|
|
58
|
+
| 2 | Bundle Size Optimization | **CRITICAL** | `bundle-` |
|
|
59
|
+
| 3 | Server-Side Performance | HIGH | `server-` |
|
|
60
|
+
| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
|
|
61
|
+
| 5 | Re-render Optimization | MEDIUM | `rerender-` |
|
|
62
|
+
| 6 | Rendering Performance | MEDIUM | `rendering-` |
|
|
63
|
+
| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
|
|
64
|
+
|
|
65
|
+
</categories>
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<rules>
|
|
70
|
+
|
|
71
|
+
## Quick Reference
|
|
72
|
+
|
|
73
|
+
### 1. Eliminating Waterfalls (CRITICAL)
|
|
74
|
+
|
|
75
|
+
| Rule | Description |
|
|
76
|
+
|------|-------------|
|
|
77
|
+
| `async-defer-await` | Move await into branches where actually used |
|
|
78
|
+
| `async-parallel` | Use Promise.all() for independent operations |
|
|
79
|
+
| `async-dependencies` | Use better-all for partial dependencies |
|
|
80
|
+
| `async-loader` | Parallel data fetching in TanStack Router loader |
|
|
81
|
+
|
|
82
|
+
### 2. Bundle Size Optimization (CRITICAL)
|
|
83
|
+
|
|
84
|
+
| Rule | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `bundle-barrel-imports` | Import directly, avoid barrel files |
|
|
87
|
+
| `bundle-lazy-routes` | Route-based code splitting |
|
|
88
|
+
| `bundle-defer-third-party` | Load analytics/logging after hydration |
|
|
89
|
+
| `bundle-conditional` | Load modules only when feature is activated |
|
|
90
|
+
| `bundle-preload` | Preload on hover/focus for perceived speed |
|
|
91
|
+
|
|
92
|
+
### 3. Server-Side Performance (HIGH)
|
|
93
|
+
|
|
94
|
+
| Rule | Description |
|
|
95
|
+
|------|-------------|
|
|
96
|
+
| `server-cache-lru` | Use LRU cache for cross-request caching |
|
|
97
|
+
| `server-serialization` | Minimize data passed to client components |
|
|
98
|
+
| `server-parallel-fetching` | Parallel data fetching in loader |
|
|
99
|
+
| `server-deferred-data` | Use defer() for non-blocking data loading |
|
|
100
|
+
|
|
101
|
+
### 4. Client-Side Data Fetching (MEDIUM-HIGH)
|
|
102
|
+
|
|
103
|
+
| Rule | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| `client-tanstack-query` | TanStack Query for automatic caching/deduplication |
|
|
106
|
+
| `client-event-listeners` | Deduplicate global event listeners |
|
|
107
|
+
|
|
108
|
+
### 5. Re-render Optimization (MEDIUM)
|
|
109
|
+
|
|
110
|
+
| Rule | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `rerender-defer-reads` | Don't subscribe to state only used in callbacks |
|
|
113
|
+
| `rerender-memo` | Extract expensive work into memoized components |
|
|
114
|
+
| `rerender-dependencies` | Use primitive dependencies in effects |
|
|
115
|
+
| `rerender-derived-state` | Subscribe to derived booleans, not raw values |
|
|
116
|
+
| `rerender-functional-setstate` | Use functional setState for stable callbacks |
|
|
117
|
+
| `rerender-lazy-state-init` | Pass function to useState for expensive values |
|
|
118
|
+
| `rerender-transitions` | Use startTransition for non-urgent updates |
|
|
119
|
+
|
|
120
|
+
### 6. Rendering Performance (MEDIUM)
|
|
121
|
+
|
|
122
|
+
| Rule | Description |
|
|
123
|
+
|------|-------------|
|
|
124
|
+
| `rendering-animate-svg-wrapper` | Animate div wrapper, not SVG element |
|
|
125
|
+
| `rendering-content-visibility` | Use content-visibility for long lists |
|
|
126
|
+
| `rendering-hoist-jsx` | Extract static JSX outside components |
|
|
127
|
+
| `rendering-svg-precision` | Reduce SVG coordinate precision |
|
|
128
|
+
| `rendering-conditional-render` | Use ternary, not && for conditionals |
|
|
129
|
+
|
|
130
|
+
### 7. JavaScript Performance (LOW-MEDIUM)
|
|
131
|
+
|
|
132
|
+
| Rule | Description |
|
|
133
|
+
|------|-------------|
|
|
134
|
+
| `js-batch-dom-css` | Group CSS changes via classes or cssText |
|
|
135
|
+
| `js-index-maps` | Build Map for repeated lookups |
|
|
136
|
+
| `js-cache-property-access` | Cache object properties in loops |
|
|
137
|
+
| `js-cache-function-results` | Cache function results in module-level Map |
|
|
138
|
+
| `js-cache-storage` | Cache localStorage/sessionStorage reads |
|
|
139
|
+
| `js-combine-iterations` | Combine multiple filter/map into one loop |
|
|
140
|
+
| `js-length-check-first` | Check array length before expensive comparison |
|
|
141
|
+
| `js-early-exit` | Return early from functions |
|
|
142
|
+
| `js-hoist-regexp` | Hoist RegExp creation outside loops |
|
|
143
|
+
| `js-min-max-loop` | Use loop for min/max instead of sort |
|
|
144
|
+
| `js-set-map-lookups` | Use Set/Map for O(1) lookups |
|
|
145
|
+
| `js-tosorted-immutable` | Use toSorted() for immutability |
|
|
146
|
+
|
|
147
|
+
</rules>
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
<patterns>
|
|
152
|
+
|
|
153
|
+
## Core Patterns
|
|
154
|
+
|
|
155
|
+
### ✅ Eliminate Waterfalls
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// ❌ Sequential execution, 3 round trips
|
|
159
|
+
const user = await fetchUser()
|
|
160
|
+
const posts = await fetchPosts()
|
|
161
|
+
const comments = await fetchComments()
|
|
162
|
+
|
|
163
|
+
// ✅ Parallel execution, 1 round trip
|
|
164
|
+
const [user, posts, comments] = await Promise.all([
|
|
165
|
+
fetchUser(),
|
|
166
|
+
fetchPosts(),
|
|
167
|
+
fetchComments()
|
|
168
|
+
])
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### ✅ TanStack Router Loader + createServerFn
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
175
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
176
|
+
|
|
177
|
+
// Define Server Functions
|
|
178
|
+
const getPost = createServerFn().handler(async (postId: string) => {
|
|
179
|
+
return await db.post.findUnique({ where: { id: postId } })
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const getComments = createServerFn().handler(async (postId: string) => {
|
|
183
|
+
return await db.comment.findMany({ where: { postId } })
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ❌ Sequential loading
|
|
187
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
188
|
+
loader: async ({ params }) => {
|
|
189
|
+
const post = await getPost(params.postId)
|
|
190
|
+
const comments = await getComments(params.postId)
|
|
191
|
+
return { post, comments }
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// ✅ Parallel loading
|
|
196
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
197
|
+
loader: async ({ params }) => {
|
|
198
|
+
const [post, comments] = await Promise.all([
|
|
199
|
+
getPost(params.postId),
|
|
200
|
+
getComments(params.postId)
|
|
201
|
+
])
|
|
202
|
+
return { post, comments }
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### ✅ Bundle Optimization
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// ❌ Import entire library (1583 modules, ~2.8s)
|
|
211
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
212
|
+
|
|
213
|
+
// ✅ Direct imports (3 modules only)
|
|
214
|
+
import Check from 'lucide-react/dist/esm/icons/check'
|
|
215
|
+
import X from 'lucide-react/dist/esm/icons/x'
|
|
216
|
+
import Menu from 'lucide-react/dist/esm/icons/menu'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### ✅ TanStack Query for Caching
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { useQuery } from '@tanstack/react-query'
|
|
223
|
+
|
|
224
|
+
// ❌ No deduplication, each instance fetches
|
|
225
|
+
function UserList() {
|
|
226
|
+
const [users, setUsers] = useState([])
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
fetch('/api/users')
|
|
229
|
+
.then(r => r.json())
|
|
230
|
+
.then(setUsers)
|
|
231
|
+
}, [])
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ✅ Multiple instances share one request
|
|
235
|
+
function UserList() {
|
|
236
|
+
const { data: users } = useQuery({
|
|
237
|
+
queryKey: ['users'],
|
|
238
|
+
queryFn: fetchUsers
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### ✅ Re-render Optimization
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// ❌ items as dependency, recreated every time
|
|
247
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
248
|
+
setItems([...items, ...newItems])
|
|
249
|
+
}, [items])
|
|
250
|
+
|
|
251
|
+
// ✅ Stable callback, never recreated
|
|
252
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
253
|
+
setItems(curr => [...curr, ...newItems])
|
|
254
|
+
}, [])
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
</patterns>
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
<tanstack_specific>
|
|
262
|
+
|
|
263
|
+
## TanStack Start Specific Patterns
|
|
264
|
+
|
|
265
|
+
### createServerFn for Server Functions
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
269
|
+
import { z } from 'zod'
|
|
270
|
+
|
|
271
|
+
// ✅ Basic Server Function
|
|
272
|
+
const getUser = createServerFn().handler(async () => {
|
|
273
|
+
// Only runs on server
|
|
274
|
+
return await db.user.findMany()
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// ✅ POST + Validation
|
|
278
|
+
const createUserSchema = z.object({
|
|
279
|
+
name: z.string().min(1),
|
|
280
|
+
email: z.string().email()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const createUser = createServerFn({ method: 'POST' })
|
|
284
|
+
.inputValidator(createUserSchema)
|
|
285
|
+
.handler(async ({ data }) => {
|
|
286
|
+
// data is fully typed and validated
|
|
287
|
+
return await db.user.create({ data })
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Loader Optimization
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
295
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
296
|
+
|
|
297
|
+
const getUser = createServerFn().handler(async () => {
|
|
298
|
+
return await db.user.findMany()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const getStats = createServerFn().handler(async () => {
|
|
302
|
+
return await db.stats.findMany()
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// ✅ Parallel data fetching
|
|
306
|
+
export const Route = createFileRoute('/dashboard')({
|
|
307
|
+
loader: async () => {
|
|
308
|
+
const [user, stats] = await Promise.all([
|
|
309
|
+
getUser(),
|
|
310
|
+
getStats()
|
|
311
|
+
])
|
|
312
|
+
return { user, stats }
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Deferred Data (Automatic Handling)
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
321
|
+
import { createFileRoute, Await } from '@tanstack/react-router'
|
|
322
|
+
import { Suspense } from 'react'
|
|
323
|
+
|
|
324
|
+
const getPost = createServerFn().handler(async (postId: string) => {
|
|
325
|
+
return await db.post.findUnique({ where: { id: postId } })
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const getComments = createServerFn().handler(async (postId: string) => {
|
|
329
|
+
await new Promise(r => setTimeout(r, 3000)) // Slow query simulation
|
|
330
|
+
return await db.comment.findMany({ where: { postId } })
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// ✅ Await important data, defer non-critical data
|
|
334
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
335
|
+
loader: async ({ params }) => {
|
|
336
|
+
// Fast data: await
|
|
337
|
+
const post = await getPost(params.postId)
|
|
338
|
+
|
|
339
|
+
// Slow data: return Promise (automatically deferred)
|
|
340
|
+
const deferredComments = getComments(params.postId)
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
post,
|
|
344
|
+
deferredComments
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// Handle with Await in component
|
|
350
|
+
function PostPage() {
|
|
351
|
+
const { post, deferredComments } = Route.useLoaderData()
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div>
|
|
355
|
+
<PostContent post={post} />
|
|
356
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
357
|
+
<Await promise={deferredComments}>
|
|
358
|
+
{(comments) => <Comments comments={comments} />}
|
|
359
|
+
</Await>
|
|
360
|
+
</Suspense>
|
|
361
|
+
</div>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Route-Based Code Splitting
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { lazy } from 'react'
|
|
370
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
371
|
+
|
|
372
|
+
// ✅ Heavy components lazy loaded
|
|
373
|
+
const HeavyEditor = lazy(() => import('./components/HeavyEditor'))
|
|
374
|
+
|
|
375
|
+
export const Route = createFileRoute('/editor')({
|
|
376
|
+
component: () => (
|
|
377
|
+
<Suspense fallback={<EditorSkeleton />}>
|
|
378
|
+
<HeavyEditor />
|
|
379
|
+
</Suspense>
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
</tanstack_specific>
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
<usage>
|
|
389
|
+
|
|
390
|
+
## Usage
|
|
391
|
+
|
|
392
|
+
**Detailed rules and examples:**
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
rules/async-parallel.md
|
|
396
|
+
rules/bundle-barrel-imports.md
|
|
397
|
+
rules/client-tanstack-query.md
|
|
398
|
+
rules/server-deferred-data.md
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Each rule file contains:
|
|
402
|
+
- Why it matters
|
|
403
|
+
- ❌ Incorrect code example with explanation
|
|
404
|
+
- ✅ Correct code example with explanation
|
|
405
|
+
- Additional context and references
|
|
406
|
+
|
|
407
|
+
**Full compiled document:** `AGENTS.md`
|
|
408
|
+
|
|
409
|
+
</usage>
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
<references>
|
|
414
|
+
|
|
415
|
+
## References
|
|
416
|
+
|
|
417
|
+
### TanStack Official Documentation
|
|
418
|
+
1. [React](https://react.dev)
|
|
419
|
+
2. [TanStack Start Overview](https://tanstack.com/start/latest/docs/framework/react/overview)
|
|
420
|
+
3. [TanStack Start Quick Start](https://tanstack.com/start/latest/docs/framework/react/quick-start)
|
|
421
|
+
4. [TanStack Router](https://tanstack.com/router)
|
|
422
|
+
5. [TanStack Router Deferred Data Loading](https://tanstack.com/router/v1/docs/framework/react/guide/deferred-data-loading)
|
|
423
|
+
6. [TanStack Query](https://tanstack.com/query)
|
|
424
|
+
7. [Server Functions Guide](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions)
|
|
425
|
+
|
|
426
|
+
### External Resources
|
|
427
|
+
8. [better-all](https://github.com/shuding/better-all)
|
|
428
|
+
9. [node-lru-cache](https://github.com/isaacs/node-lru-cache)
|
|
429
|
+
10. [Using Server Functions and TanStack Query](https://www.brenelz.com/posts/using-server-functions-and-tanstack-query/)
|
|
430
|
+
|
|
431
|
+
</references>
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-defer-await.md
ADDED
|
@@ -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.
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/async-dependencies.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dependency-Based Parallelization
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: 2-10× improvement
|
|
5
|
+
tags: async, parallelization, dependencies, better-all
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Dependency-Based Parallelization
|
|
9
|
+
|
|
10
|
+
For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
|
|
11
|
+
|
|
12
|
+
**Incorrect (profile waits for config unnecessarily):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const [user, config] = await Promise.all([
|
|
16
|
+
fetchUser(),
|
|
17
|
+
fetchConfig()
|
|
18
|
+
])
|
|
19
|
+
const profile = await fetchProfile(user.id)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (config and profile run in parallel):**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { all } from 'better-all'
|
|
26
|
+
|
|
27
|
+
const { user, config, profile } = await all({
|
|
28
|
+
async user() { return fetchUser() },
|
|
29
|
+
async config() { return fetchConfig() },
|
|
30
|
+
async profile() {
|
|
31
|
+
return fetchProfile((await this.$.user).id)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Parallel Data Fetching in TanStack Router Loader
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: 2-10× improvement
|
|
5
|
+
tags: async, loader, tanstack-router, parallelization, waterfalls
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Parallel Data Fetching in TanStack Router Loader
|
|
9
|
+
|
|
10
|
+
Execute independent server function calls concurrently in `loader` using `Promise.all()`. This eliminates request waterfalls.
|
|
11
|
+
|
|
12
|
+
**Incorrect (sequential execution, 2 round trips):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
16
|
+
import { getUser, getPosts } from '@/functions/data'
|
|
17
|
+
|
|
18
|
+
export const Route = createFileRoute('/dashboard')({
|
|
19
|
+
loader: async () => {
|
|
20
|
+
const user = await getUser()
|
|
21
|
+
const posts = await getPosts()
|
|
22
|
+
return { user, posts }
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (parallel execution, 1 round trip):**
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
31
|
+
import { getUser, getPosts } from '@/functions/data'
|
|
32
|
+
|
|
33
|
+
export const Route = createFileRoute('/dashboard')({
|
|
34
|
+
loader: async () => {
|
|
35
|
+
const [user, posts] = await Promise.all([
|
|
36
|
+
getUser(),
|
|
37
|
+
getPosts()
|
|
38
|
+
])
|
|
39
|
+
return { user, posts }
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This pattern works with any independent `createServerFn()` calls, reducing page load time by 50-80%.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Promise.all() for Independent Operations
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: 2-10× improvement
|
|
5
|
+
tags: async, parallelization, promises, waterfalls
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Promise.all() for Independent Operations
|
|
9
|
+
|
|
10
|
+
When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (sequential execution, 3 round trips):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const user = await fetchUser()
|
|
16
|
+
const posts = await fetchPosts()
|
|
17
|
+
const comments = await fetchComments()
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (parallel execution, 1 round trip):**
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
const [user, posts, comments] = await Promise.all([
|
|
24
|
+
fetchUser(),
|
|
25
|
+
fetchPosts(),
|
|
26
|
+
fetchComments()
|
|
27
|
+
])
|
|
28
|
+
```
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-barrel-imports.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Barrel File Imports
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: 200-800ms import cost, slow builds
|
|
5
|
+
tags: bundle, imports, tree-shaking, barrel-files, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Barrel File Imports
|
|
9
|
+
|
|
10
|
+
Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
|
|
11
|
+
|
|
12
|
+
Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
|
|
13
|
+
|
|
14
|
+
**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
|
|
15
|
+
|
|
16
|
+
**Incorrect (imports entire library):**
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
20
|
+
// Loads 1,583 modules, takes ~2.8s extra in dev
|
|
21
|
+
// Runtime cost: 200-800ms on every cold start
|
|
22
|
+
|
|
23
|
+
import { Button, TextField } from '@mui/material'
|
|
24
|
+
// Loads 2,225 modules, takes ~4.2s extra in dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (imports only what you need):**
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import Check from 'lucide-react/dist/esm/icons/check'
|
|
31
|
+
import X from 'lucide-react/dist/esm/icons/x'
|
|
32
|
+
import Menu from 'lucide-react/dist/esm/icons/menu'
|
|
33
|
+
// Loads only 3 modules (~2KB vs ~1MB)
|
|
34
|
+
|
|
35
|
+
import Button from '@mui/material/Button'
|
|
36
|
+
import TextField from '@mui/material/TextField'
|
|
37
|
+
// Loads only what you use
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Alternative (Next.js 13.5+):**
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// next.config.js - use optimizePackageImports
|
|
44
|
+
module.exports = {
|
|
45
|
+
experimental: {
|
|
46
|
+
optimizePackageImports: ['lucide-react', '@mui/material']
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Then you can keep the ergonomic barrel imports:
|
|
51
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
52
|
+
// Automatically transformed to direct imports at build time
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
|
|
56
|
+
|
|
57
|
+
Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
|
|
58
|
+
|
|
59
|
+
Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
|