@kood/claude-code 0.3.11 → 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/nextjs-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/advanced-use-latest.md +49 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/async-api-routes.md +38 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/async-defer-await.md +80 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/async-dependencies.md +36 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/async-parallel.md +28 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/bundle-conditional.md +31 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/bundle-preload.md +50 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/client-event-listeners.md +74 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-cache-storage.md +70 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-early-exit.md +50 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-index-maps.md +37 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-length-check-first.md +49 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-activity.md +26 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-memo.md +44 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/rerender-transitions.md +40 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/server-cache-lru.md +41 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/server-cache-react.md +26 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/templates/.claude/skills/nextjs-react-best-practices/rules/server-serialization.md +38 -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/vs-design-diverge/SKILL.md +307 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
# TanStack Start React Best Practices
|
|
2
|
+
|
|
3
|
+
**Version 1.0.0**
|
|
4
|
+
TanStack Start Optimization Guide
|
|
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 TanStack Start 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 TanStack Start applications, designed for AI agents and LLMs. Contains 38 rules across 7 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (JavaScript performance). 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-loader.md
|
|
47
|
+
@rules/bundle-barrel-imports.md
|
|
48
|
+
@rules/bundle-lazy-routes.md
|
|
49
|
+
@rules/bundle-conditional.md
|
|
50
|
+
@rules/bundle-defer-third-party.md
|
|
51
|
+
@rules/bundle-preload.md
|
|
52
|
+
@rules/server-cache-lru.md
|
|
53
|
+
@rules/server-serialization.md
|
|
54
|
+
@rules/server-parallel-fetching.md
|
|
55
|
+
@rules/server-deferred-data.md
|
|
56
|
+
@rules/client-tanstack-query.md
|
|
57
|
+
@rules/client-event-listeners.md
|
|
58
|
+
@rules/rerender-defer-reads.md
|
|
59
|
+
@rules/rerender-memo.md
|
|
60
|
+
@rules/rerender-dependencies.md
|
|
61
|
+
@rules/rerender-derived-state.md
|
|
62
|
+
@rules/rerender-functional-setstate.md
|
|
63
|
+
@rules/rerender-lazy-state-init.md
|
|
64
|
+
@rules/rerender-transitions.md
|
|
65
|
+
@rules/rendering-animate-svg-wrapper.md
|
|
66
|
+
@rules/rendering-content-visibility.md
|
|
67
|
+
@rules/rendering-hoist-jsx.md
|
|
68
|
+
@rules/rendering-svg-precision.md
|
|
69
|
+
@rules/rendering-conditional-render.md
|
|
70
|
+
@rules/js-batch-dom-css.md
|
|
71
|
+
@rules/js-index-maps.md
|
|
72
|
+
@rules/js-cache-property-access.md
|
|
73
|
+
@rules/js-cache-function-results.md
|
|
74
|
+
@rules/js-cache-storage.md
|
|
75
|
+
@rules/js-combine-iterations.md
|
|
76
|
+
@rules/js-length-check-first.md
|
|
77
|
+
@rules/js-early-exit.md
|
|
78
|
+
@rules/js-hoist-regexp.md
|
|
79
|
+
@rules/js-min-max-loop.md
|
|
80
|
+
@rules/js-set-map-lookups.md
|
|
81
|
+
@rules/js-tosorted-immutable.md
|
|
82
|
+
|
|
83
|
+
</instructions>
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
<categories>
|
|
88
|
+
|
|
89
|
+
## Categories by Priority
|
|
90
|
+
|
|
91
|
+
| Priority | Category | Impact | Description |
|
|
92
|
+
|----------|----------|--------|-------------|
|
|
93
|
+
| 1 | Eliminating Waterfalls | **CRITICAL** | Convert sequential awaits to parallel. Biggest gains |
|
|
94
|
+
| 2 | Bundle Size Optimization | **CRITICAL** | Improve TTI and LCP. Faster initial loads |
|
|
95
|
+
| 3 | Server-Side Performance | HIGH | Eliminate server-side waterfalls, reduce response times |
|
|
96
|
+
| 4 | Client-Side Data Fetching | MEDIUM-HIGH | Automatic deduplication, efficient data fetching |
|
|
97
|
+
| 5 | Re-render Optimization | MEDIUM | Minimize unnecessary re-renders, improve UI responsiveness |
|
|
98
|
+
| 6 | Rendering Performance | MEDIUM | Optimize browser rendering work |
|
|
99
|
+
| 7 | JavaScript Performance | LOW-MEDIUM | Hot path micro-optimizations |
|
|
100
|
+
|
|
101
|
+
</categories>
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
<critical_patterns>
|
|
106
|
+
|
|
107
|
+
## 1. Eliminating Waterfalls (CRITICAL)
|
|
108
|
+
|
|
109
|
+
Waterfalls are the #1 performance killer. Each sequential await adds full network latency.
|
|
110
|
+
|
|
111
|
+
### Parallel Execution
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// ❌ Sequential execution (3 round trips)
|
|
115
|
+
const user = await fetchUser()
|
|
116
|
+
const posts = await fetchPosts()
|
|
117
|
+
const comments = await fetchComments()
|
|
118
|
+
|
|
119
|
+
// ✅ Parallel execution (1 round trip)
|
|
120
|
+
const [user, posts, comments] = await Promise.all([
|
|
121
|
+
fetchUser(),
|
|
122
|
+
fetchPosts(),
|
|
123
|
+
fetchComments()
|
|
124
|
+
])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### createServerFn + Loader Parallel Fetching
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
131
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
132
|
+
|
|
133
|
+
// Define Server Functions
|
|
134
|
+
const getPost = createServerFn().handler(async (postId: string) => {
|
|
135
|
+
return await db.post.findUnique({ where: { id: postId } })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const getAuthor = createServerFn().handler(async (authorId: string) => {
|
|
139
|
+
return await db.author.findUnique({ where: { id: authorId } })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const getComments = createServerFn().handler(async (postId: string) => {
|
|
143
|
+
return await db.comment.findMany({ where: { postId } })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// ❌ Sequential loading
|
|
147
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
148
|
+
loader: async ({ params }) => {
|
|
149
|
+
const post = await getPost(params.postId)
|
|
150
|
+
const author = await getAuthor(post.authorId)
|
|
151
|
+
const comments = await getComments(params.postId)
|
|
152
|
+
return { post, author, comments }
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// ✅ Parallel loading (independent data)
|
|
157
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
158
|
+
loader: async ({ params }) => {
|
|
159
|
+
const [post, comments] = await Promise.all([
|
|
160
|
+
getPost(params.postId),
|
|
161
|
+
getComments(params.postId)
|
|
162
|
+
])
|
|
163
|
+
return { post, comments }
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Dependency-Based Parallelization
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { all } from 'better-all'
|
|
172
|
+
|
|
173
|
+
// ✅ Maximum parallelization with better-all
|
|
174
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
175
|
+
loader: async ({ params }) => {
|
|
176
|
+
const { post, author, comments } = await all({
|
|
177
|
+
async post() {
|
|
178
|
+
return fetchPost(params.postId)
|
|
179
|
+
},
|
|
180
|
+
async author() {
|
|
181
|
+
const p = await this.$.post
|
|
182
|
+
return fetchAuthor(p.authorId)
|
|
183
|
+
},
|
|
184
|
+
async comments() {
|
|
185
|
+
return fetchComments(params.postId)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
return { post, author, comments }
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Deferred Data for Non-Blocking Loading (Automatic)
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
197
|
+
import { createFileRoute, Await } from '@tanstack/react-router'
|
|
198
|
+
import { Suspense } from 'react'
|
|
199
|
+
|
|
200
|
+
// Fast Server Function
|
|
201
|
+
const getPost = createServerFn().handler(async (postId: string) => {
|
|
202
|
+
return await db.post.findUnique({ where: { id: postId } })
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Slow Server Function
|
|
206
|
+
const getComments = createServerFn().handler(async (postId: string) => {
|
|
207
|
+
await new Promise(r => setTimeout(r, 3000)) // Slow query
|
|
208
|
+
return await db.comment.findMany({ where: { postId } })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// ✅ Await important data, defer non-critical data
|
|
212
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
213
|
+
loader: async ({ params }) => {
|
|
214
|
+
// Important: post loads immediately (await)
|
|
215
|
+
const post = await getPost(params.postId)
|
|
216
|
+
|
|
217
|
+
// Non-critical: comments returns Promise (automatically deferred)
|
|
218
|
+
const deferredComments = getComments(params.postId)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
post,
|
|
222
|
+
deferredComments
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
function PostPage() {
|
|
228
|
+
const { post, deferredComments } = Route.useLoaderData()
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div>
|
|
232
|
+
{/* post renders immediately */}
|
|
233
|
+
<PostContent post={post} />
|
|
234
|
+
|
|
235
|
+
{/* comments stream in */}
|
|
236
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
237
|
+
<Await promise={deferredComments}>
|
|
238
|
+
{(comments) => <Comments comments={comments} />}
|
|
239
|
+
</Await>
|
|
240
|
+
</Suspense>
|
|
241
|
+
</div>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Important:** TanStack Start doesn't require calling `defer()` explicitly. Simply return a Promise and it's automatically deferred.
|
|
247
|
+
|
|
248
|
+
</critical_patterns>
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
<bundle_optimization>
|
|
253
|
+
|
|
254
|
+
## 2. Bundle Size Optimization (CRITICAL)
|
|
255
|
+
|
|
256
|
+
### Avoid Barrel File Imports
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
// ❌ Import entire library (1583 modules, ~2.8s)
|
|
260
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
261
|
+
|
|
262
|
+
// ✅ Direct imports (3 modules only)
|
|
263
|
+
import Check from 'lucide-react/dist/esm/icons/check'
|
|
264
|
+
import X from 'lucide-react/dist/esm/icons/x'
|
|
265
|
+
import Menu from 'lucide-react/dist/esm/icons/menu'
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
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`
|
|
269
|
+
|
|
270
|
+
### Route-Based Code Splitting
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { lazy } from 'react'
|
|
274
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
275
|
+
|
|
276
|
+
// ❌ All components bundled in main chunk
|
|
277
|
+
import { HeavyEditor } from './components/HeavyEditor'
|
|
278
|
+
import { ComplexChart } from './components/ComplexChart'
|
|
279
|
+
|
|
280
|
+
export const Route = createFileRoute('/dashboard')({
|
|
281
|
+
component: DashboardPage
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
function DashboardPage() {
|
|
285
|
+
return (
|
|
286
|
+
<div>
|
|
287
|
+
<HeavyEditor />
|
|
288
|
+
<ComplexChart />
|
|
289
|
+
</div>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ✅ Heavy components lazy loaded
|
|
294
|
+
const HeavyEditor = lazy(() => import('./components/HeavyEditor'))
|
|
295
|
+
const ComplexChart = lazy(() => import('./components/ComplexChart'))
|
|
296
|
+
|
|
297
|
+
export const Route = createFileRoute('/dashboard')({
|
|
298
|
+
component: DashboardPage
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
function DashboardPage() {
|
|
302
|
+
return (
|
|
303
|
+
<div>
|
|
304
|
+
<Suspense fallback={<EditorSkeleton />}>
|
|
305
|
+
<HeavyEditor />
|
|
306
|
+
</Suspense>
|
|
307
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
308
|
+
<ComplexChart />
|
|
309
|
+
</Suspense>
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Conditional Module Loading
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
// ❌ Always loaded
|
|
319
|
+
import { AnimationFrames } from './animation-frames'
|
|
320
|
+
|
|
321
|
+
function AnimationPlayer({ enabled }: { enabled: boolean }) {
|
|
322
|
+
if (!enabled) return null
|
|
323
|
+
return <Canvas frames={AnimationFrames} />
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ✅ Load only when activated
|
|
327
|
+
function AnimationPlayer({ enabled }: { enabled: boolean }) {
|
|
328
|
+
const [frames, setFrames] = useState<Frame[] | null>(null)
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (enabled && !frames) {
|
|
332
|
+
import('./animation-frames')
|
|
333
|
+
.then(mod => setFrames(mod.frames))
|
|
334
|
+
.catch(() => setEnabled(false))
|
|
335
|
+
}
|
|
336
|
+
}, [enabled, frames])
|
|
337
|
+
|
|
338
|
+
if (!frames) return <Skeleton />
|
|
339
|
+
return <Canvas frames={frames} />
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
</bundle_optimization>
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
<server_performance>
|
|
348
|
+
|
|
349
|
+
## 3. Server-Side Performance (HIGH)
|
|
350
|
+
|
|
351
|
+
### LRU Cache - Cross-Request Caching
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { LRUCache } from 'lru-cache'
|
|
355
|
+
|
|
356
|
+
const cache = new LRUCache<string, any>({
|
|
357
|
+
max: 1000,
|
|
358
|
+
ttl: 5 * 60 * 1000 // 5 minutes
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
export async function getUser(id: string) {
|
|
362
|
+
const cached = cache.get(id)
|
|
363
|
+
if (cached) return cached
|
|
364
|
+
|
|
365
|
+
const user = await db.user.findUnique({ where: { id } })
|
|
366
|
+
cache.set(id, user)
|
|
367
|
+
return user
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Request 1: DB query, result cached
|
|
371
|
+
// Request 2: cache hit, no DB query
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Minimize Serialization
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// ❌ Serializes all 50 fields
|
|
378
|
+
export const Route = createFileRoute('/profile')({
|
|
379
|
+
loader: async () => {
|
|
380
|
+
const user = await fetchUser() // 50 fields
|
|
381
|
+
return { user }
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
function ProfilePage() {
|
|
386
|
+
const { user } = Route.useLoaderData()
|
|
387
|
+
return <div>{user.name}</div> // uses 1 field
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ✅ Serializes only needed fields
|
|
391
|
+
export const Route = createFileRoute('/profile')({
|
|
392
|
+
loader: async () => {
|
|
393
|
+
const user = await fetchUser()
|
|
394
|
+
return {
|
|
395
|
+
userName: user.name,
|
|
396
|
+
userEmail: user.email
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
function ProfilePage() {
|
|
402
|
+
const { userName, userEmail } = Route.useLoaderData()
|
|
403
|
+
return (
|
|
404
|
+
<div>
|
|
405
|
+
<div>{userName}</div>
|
|
406
|
+
<div>{userEmail}</div>
|
|
407
|
+
</div>
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Parallel Fetching in Loader + createServerFn
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
416
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
417
|
+
|
|
418
|
+
// Define Server Functions
|
|
419
|
+
const getUser = createServerFn().handler(async () => {
|
|
420
|
+
return await db.user.findMany()
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const getStats = createServerFn().handler(async () => {
|
|
424
|
+
return await db.stats.findMany()
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const getNotifications = createServerFn().handler(async () => {
|
|
428
|
+
return await db.notification.findMany()
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
// ❌ Sequential fetching
|
|
432
|
+
export const Route = createFileRoute('/dashboard')({
|
|
433
|
+
loader: async () => {
|
|
434
|
+
const user = await getUser()
|
|
435
|
+
const stats = await getStats()
|
|
436
|
+
const notifications = await getNotifications()
|
|
437
|
+
return { user, stats, notifications }
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// ✅ Parallel fetching
|
|
442
|
+
export const Route = createFileRoute('/dashboard')({
|
|
443
|
+
loader: async () => {
|
|
444
|
+
const [user, stats, notifications] = await Promise.all([
|
|
445
|
+
getUser(),
|
|
446
|
+
getStats(),
|
|
447
|
+
getNotifications()
|
|
448
|
+
])
|
|
449
|
+
return { user, stats, notifications }
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Important:** TanStack Start loaders are isomorphic. They run on both server and client, so separate server-only code with `createServerFn()`.
|
|
455
|
+
|
|
456
|
+
</server_performance>
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
<client_data_fetching>
|
|
461
|
+
|
|
462
|
+
## 4. Client-Side Data Fetching (MEDIUM-HIGH)
|
|
463
|
+
|
|
464
|
+
### TanStack Query for Automatic Caching
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
import { useQuery } from '@tanstack/react-query'
|
|
468
|
+
|
|
469
|
+
// ❌ No deduplication, each instance fetches
|
|
470
|
+
function UserList() {
|
|
471
|
+
const [users, setUsers] = useState([])
|
|
472
|
+
useEffect(() => {
|
|
473
|
+
fetch('/api/users')
|
|
474
|
+
.then(r => r.json())
|
|
475
|
+
.then(setUsers)
|
|
476
|
+
}, [])
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ✅ Multiple instances share one request
|
|
480
|
+
function UserList() {
|
|
481
|
+
const { data: users } = useQuery({
|
|
482
|
+
queryKey: ['users'],
|
|
483
|
+
queryFn: fetchUsers
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### TanStack Query Mutations
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
492
|
+
|
|
493
|
+
function UpdateButton() {
|
|
494
|
+
const queryClient = useQueryClient()
|
|
495
|
+
|
|
496
|
+
const mutation = useMutation({
|
|
497
|
+
mutationFn: updateUser,
|
|
498
|
+
onSuccess: () => {
|
|
499
|
+
// Invalidate cache for automatic refetch
|
|
500
|
+
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<button onClick={() => mutation.mutate()}>
|
|
506
|
+
Update
|
|
507
|
+
</button>
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
</client_data_fetching>
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
<rerender_optimization>
|
|
517
|
+
|
|
518
|
+
## 5. Re-render Optimization (MEDIUM)
|
|
519
|
+
|
|
520
|
+
### Functional setState
|
|
521
|
+
|
|
522
|
+
```tsx
|
|
523
|
+
// ❌ items as dependency, recreated every time
|
|
524
|
+
function TodoList() {
|
|
525
|
+
const [items, setItems] = useState(initialItems)
|
|
526
|
+
|
|
527
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
528
|
+
setItems([...items, ...newItems])
|
|
529
|
+
}, [items]) // Recreated on items change
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ✅ Stable callback, never recreated
|
|
533
|
+
function TodoList() {
|
|
534
|
+
const [items, setItems] = useState(initialItems)
|
|
535
|
+
|
|
536
|
+
const addItems = useCallback((newItems: Item[]) => {
|
|
537
|
+
setItems(curr => [...curr, ...newItems])
|
|
538
|
+
}, []) // No dependencies, always uses latest state
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Lazy State Initialization
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
// ❌ Runs on every render
|
|
546
|
+
function FilteredList({ items }: { items: Item[] }) {
|
|
547
|
+
const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ✅ Runs only on initial render
|
|
551
|
+
function FilteredList({ items }: { items: Item[] }) {
|
|
552
|
+
const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Subscribe to Derived State
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
// ❌ Re-renders on every pixel change
|
|
560
|
+
function Sidebar() {
|
|
561
|
+
const width = useWindowWidth() // continuous updates
|
|
562
|
+
const isMobile = width < 768
|
|
563
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ✅ Re-renders only when boolean changes
|
|
567
|
+
function Sidebar() {
|
|
568
|
+
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
569
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
</rerender_optimization>
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
<rendering_performance>
|
|
578
|
+
|
|
579
|
+
## 6. Rendering Performance (MEDIUM)
|
|
580
|
+
|
|
581
|
+
### Animate SVG Wrapper
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
// ❌ Animating SVG directly - no hardware acceleration
|
|
585
|
+
function LoadingSpinner() {
|
|
586
|
+
return (
|
|
587
|
+
<svg className="animate-spin" width="24" height="24">
|
|
588
|
+
<circle cx="12" cy="12" r="10" />
|
|
589
|
+
</svg>
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ✅ Animating wrapper div - hardware accelerated
|
|
594
|
+
function LoadingSpinner() {
|
|
595
|
+
return (
|
|
596
|
+
<div className="animate-spin">
|
|
597
|
+
<svg width="24" height="24">
|
|
598
|
+
<circle cx="12" cy="12" r="10" />
|
|
599
|
+
</svg>
|
|
600
|
+
</div>
|
|
601
|
+
)
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### content-visibility
|
|
606
|
+
|
|
607
|
+
```css
|
|
608
|
+
.message-item {
|
|
609
|
+
content-visibility: auto;
|
|
610
|
+
contain-intrinsic-size: 0 80px;
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
```tsx
|
|
615
|
+
function MessageList({ messages }: { messages: Message[] }) {
|
|
616
|
+
return (
|
|
617
|
+
<div className="overflow-y-auto h-screen">
|
|
618
|
+
{messages.map(msg => (
|
|
619
|
+
<div key={msg.id} className="message-item">
|
|
620
|
+
<Avatar user={msg.author} />
|
|
621
|
+
<div>{msg.content}</div>
|
|
622
|
+
</div>
|
|
623
|
+
))}
|
|
624
|
+
</div>
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render)
|
|
630
|
+
|
|
631
|
+
### JSX Hoisting
|
|
632
|
+
|
|
633
|
+
```tsx
|
|
634
|
+
// ❌ Recreated every render
|
|
635
|
+
function Container() {
|
|
636
|
+
return (
|
|
637
|
+
<div>
|
|
638
|
+
{loading && <div className="animate-pulse h-20 bg-gray-200" />}
|
|
639
|
+
</div>
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ✅ Created once
|
|
644
|
+
const loadingSkeleton = (
|
|
645
|
+
<div className="animate-pulse h-20 bg-gray-200" />
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
function Container() {
|
|
649
|
+
return (
|
|
650
|
+
<div>
|
|
651
|
+
{loading && loadingSkeleton}
|
|
652
|
+
</div>
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
</rendering_performance>
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
<javascript_performance>
|
|
662
|
+
|
|
663
|
+
## 7. JavaScript Performance (LOW-MEDIUM)
|
|
664
|
+
|
|
665
|
+
### Build Index Maps for Repeated Lookups
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
// ❌ O(n) per lookup
|
|
669
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
670
|
+
return orders.map(order => ({
|
|
671
|
+
...order,
|
|
672
|
+
user: users.find(u => u.id === order.userId)
|
|
673
|
+
}))
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ✅ O(1) per lookup
|
|
677
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
678
|
+
const userById = new Map(users.map(u => [u.id, u]))
|
|
679
|
+
return orders.map(order => ({
|
|
680
|
+
...order,
|
|
681
|
+
user: userById.get(order.userId)
|
|
682
|
+
}))
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
1000 orders × 1000 users: 1M ops → 2K ops
|
|
687
|
+
|
|
688
|
+
### Early Length Check for Array Comparisons
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
// ❌ Always runs expensive comparison
|
|
692
|
+
function hasChanges(current: string[], original: string[]) {
|
|
693
|
+
return current.sort().join() !== original.sort().join()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ✅ O(1) length check first
|
|
697
|
+
function hasChanges(current: string[], original: string[]) {
|
|
698
|
+
if (current.length !== original.length) return true
|
|
699
|
+
|
|
700
|
+
const currentSorted = current.toSorted()
|
|
701
|
+
const originalSorted = original.toSorted()
|
|
702
|
+
for (let i = 0; i < currentSorted.length; i++) {
|
|
703
|
+
if (currentSorted[i] !== originalSorted[i]) return true
|
|
704
|
+
}
|
|
705
|
+
return false
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Use toSorted() for Immutability
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
// ❌ Mutates original array
|
|
713
|
+
function UserList({ users }: { users: User[] }) {
|
|
714
|
+
const sorted = useMemo(
|
|
715
|
+
() => users.sort((a, b) => a.name.localeCompare(b.name)),
|
|
716
|
+
[users]
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ✅ Creates new array
|
|
721
|
+
function UserList({ users }: { users: User[] }) {
|
|
722
|
+
const sorted = useMemo(
|
|
723
|
+
() => users.toSorted((a, b) => a.name.localeCompare(b.name)),
|
|
724
|
+
[users]
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
</javascript_performance>
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
<references>
|
|
734
|
+
|
|
735
|
+
## References
|
|
736
|
+
|
|
737
|
+
### TanStack Official Documentation
|
|
738
|
+
1. [React](https://react.dev)
|
|
739
|
+
2. [TanStack Start Overview](https://tanstack.com/start/latest/docs/framework/react/overview)
|
|
740
|
+
3. [TanStack Start Quick Start](https://tanstack.com/start/latest/docs/framework/react/quick-start)
|
|
741
|
+
4. [TanStack Router](https://tanstack.com/router)
|
|
742
|
+
5. [TanStack Router Deferred Data Loading](https://tanstack.com/router/v1/docs/framework/react/guide/deferred-data-loading)
|
|
743
|
+
6. [TanStack Query](https://tanstack.com/query)
|
|
744
|
+
7. [Server Functions Guide](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions)
|
|
745
|
+
|
|
746
|
+
### External Resources
|
|
747
|
+
8. [better-all](https://github.com/shuding/better-all)
|
|
748
|
+
9. [node-lru-cache](https://github.com/isaacs/node-lru-cache)
|
|
749
|
+
10. [Using Server Functions and TanStack Query](https://www.brenelz.com/posts/using-server-functions-and-tanstack-query/)
|
|
750
|
+
|
|
751
|
+
</references>
|