dev3000 0.0.147 → 0.0.148

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.
Files changed (54) hide show
  1. package/dist/cli.js +10 -7
  2. package/dist/cli.js.map +1 -1
  3. package/dist/components/SkillSelector.d.ts +2 -2
  4. package/dist/components/SkillSelector.d.ts.map +1 -1
  5. package/dist/components/SkillSelector.js +10 -4
  6. package/dist/components/SkillSelector.js.map +1 -1
  7. package/dist/dev-environment.d.ts +38 -0
  8. package/dist/dev-environment.d.ts.map +1 -1
  9. package/dist/dev-environment.js +60 -10
  10. package/dist/dev-environment.js.map +1 -1
  11. package/dist/skills/index.d.ts +3 -2
  12. package/dist/skills/index.d.ts.map +1 -1
  13. package/dist/skills/index.js +8 -4
  14. package/dist/skills/index.js.map +1 -1
  15. package/dist/skills/index.ts +9 -4
  16. package/dist/utils/skill-installer.d.ts +4 -2
  17. package/dist/utils/skill-installer.d.ts.map +1 -1
  18. package/dist/utils/skill-installer.js +78 -10
  19. package/dist/utils/skill-installer.js.map +1 -1
  20. package/mcp-server/.next/BUILD_ID +1 -1
  21. package/mcp-server/.next/build-manifest.json +2 -2
  22. package/mcp-server/.next/fallback-build-manifest.json +2 -2
  23. package/mcp-server/.next/prerender-manifest.json +3 -3
  24. package/mcp-server/.next/server/app/_global-error.html +2 -2
  25. package/mcp-server/.next/server/app/_global-error.rsc +1 -1
  26. package/mcp-server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  27. package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/mcp-server/.next/server/app/_not-found.html +1 -1
  32. package/mcp-server/.next/server/app/_not-found.rsc +1 -1
  33. package/mcp-server/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  34. package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  36. package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/mcp-server/.next/server/app/index.html +1 -1
  40. package/mcp-server/.next/server/app/index.rsc +1 -1
  41. package/mcp-server/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  42. package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +1 -1
  43. package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  44. package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +1 -1
  45. package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  46. package/mcp-server/.next/server/pages/404.html +1 -1
  47. package/mcp-server/.next/server/pages/500.html +2 -2
  48. package/mcp-server/.next/server/server-reference-manifest.js +1 -1
  49. package/mcp-server/.next/server/server-reference-manifest.json +1 -1
  50. package/package.json +3 -3
  51. package/dist/skills/react-performance/SKILL.md +0 -1034
  52. /package/mcp-server/.next/static/{2wDj8SEBvulPHjApt1crR → 5msMCZw04bKnsG5w2WyDQ}/_buildManifest.js +0 -0
  53. /package/mcp-server/.next/static/{2wDj8SEBvulPHjApt1crR → 5msMCZw04bKnsG5w2WyDQ}/_clientMiddlewareManifest.json +0 -0
  54. /package/mcp-server/.next/static/{2wDj8SEBvulPHjApt1crR → 5msMCZw04bKnsG5w2WyDQ}/_ssgManifest.js +0 -0
@@ -1,1034 +0,0 @@
1
- ---
2
- description: React performance optimization guidelines. Use when optimizing React/Next.js apps, fixing performance issues, reducing bundle size, eliminating waterfalls, or improving rendering speed.
3
- ---
4
-
5
- # React Performance Guidelines
6
-
7
- **Version 1.0**
8
- Vercel Engineering
9
- January 2026
10
-
11
- > **Note:**
12
- > This document is mainly for agents and LLMs to follow when maintaining,
13
- > generating, or refactoring React and Next.js codebases at Vercel. Humans
14
- > may also find it useful, but guidance here is optimized for automation
15
- > and consistency by AI-assisted workflows.
16
-
17
- ---
18
-
19
- ## Abstract
20
-
21
- Performance optimization guide for React and Next.js applications,
22
- ordered by impact. Sections 1-2 yield the highest gains (2-10x
23
- improvements), sections 3-5 provide medium gains (20-50%), and
24
- sections 6-8 offer incremental improvements (5-20%) in hot paths.
25
-
26
- ---
27
-
28
- ## Table of Contents
29
-
30
- 1. [Eliminating Waterfalls](#1-eliminating-waterfalls) - **CRITICAL**
31
- 2. [Bundle Size Optimization](#2-bundle-size-optimization) - **CRITICAL**
32
- 3. [Server-Side Performance](#3-server-side-performance) - **HIGH**
33
- 4. [Client-Side Data Fetching](#4-client-side-data-fetching) - **MEDIUM-HIGH**
34
- 5. [Re-render Optimization](#5-re-render-optimization) - **MEDIUM**
35
- 6. [Rendering Performance](#6-rendering-performance) - **MEDIUM**
36
- 7. [JavaScript Performance](#7-javascript-performance) - **LOW-MEDIUM**
37
- 8. [Advanced Patterns](#8-advanced-patterns) - **LOW**
38
-
39
- ---
40
-
41
- ## 1. Eliminating Waterfalls
42
-
43
- **Impact: CRITICAL (2-10x improvement)**
44
-
45
- Waterfalls are the #1 performance killer. Each sequential await
46
- adds full network latency. Eliminating them yields the largest
47
- gains.
48
-
49
- ### 1.1 Promise.all() for Independent Operations
50
-
51
- When async operations have no interdependencies, execute them
52
- concurrently using `Promise.all()`.
53
-
54
- **Incorrect (sequential execution, 3 round trips):**
55
-
56
- ```typescript
57
- const user = await fetchUser()
58
- const posts = await fetchPosts()
59
- const comments = await fetchComments()
60
- ```
61
-
62
- **Correct (parallel execution, 1 round trip):**
63
-
64
- ```typescript
65
- const [user, posts, comments] = await Promise.all([
66
- fetchUser(),
67
- fetchPosts(),
68
- fetchComments()
69
- ])
70
- ```
71
-
72
- ### 1.2 Dependency-Based Parallelization
73
-
74
- For operations with partial dependencies, use `better-all` to
75
- maximize parallelism. It automatically starts each task at the
76
- earliest possible moment.
77
-
78
- **Incorrect (profile waits for config unnecessarily):**
79
-
80
- ```typescript
81
- const [user, config] = await Promise.all([
82
- fetchUser(),
83
- fetchConfig()
84
- ])
85
- const profile = await fetchProfile(user.id)
86
- ```
87
-
88
- **Correct (config and profile run in parallel):**
89
-
90
- ```typescript
91
- import { all } from 'better-all'
92
-
93
- const { user, config, profile } = await all({
94
- async user() { return fetchUser() },
95
- async config() { return fetchConfig() },
96
- async profile() {
97
- return fetchProfile((await this.$.user).id)
98
- }
99
- })
100
- ```
101
-
102
- Reference: [better-all](https://github.com/shuding/better-all)
103
-
104
- ### 1.3 Prevent Waterfall Chains in API Routes
105
-
106
- In API routes and Server Actions, start independent operations
107
- immediately, even if you don't await them yet.
108
-
109
- **Incorrect (config waits for auth, data waits for both):**
110
-
111
- ```typescript
112
- export async function GET(request: Request) {
113
- const session = await auth()
114
- const config = await fetchConfig()
115
- const data = await fetchData(session.user.id)
116
- return Response.json({ data, config })
117
- }
118
- ```
119
-
120
- **Correct (auth and config start immediately):**
121
-
122
- ```typescript
123
- export async function GET(request: Request) {
124
- const sessionPromise = auth()
125
- const configPromise = fetchConfig()
126
- const session = await sessionPromise
127
- const [config, data] = await Promise.all([
128
- configPromise,
129
- fetchData(session.user.id)
130
- ])
131
- return Response.json({ data, config })
132
- }
133
- ```
134
-
135
- ---
136
-
137
- ## 2. Bundle Size Optimization
138
-
139
- **Impact: CRITICAL (directly affects TTI and LCP)**
140
-
141
- Reducing initial bundle size improves Time to Interactive and
142
- Largest Contentful Paint.
143
-
144
- ### 2.1 Dynamic Imports for Heavy Components
145
-
146
- Use `next/dynamic` to lazy-load large components not needed on
147
- initial render.
148
-
149
- **Incorrect (Monaco bundles with main chunk ~300KB):**
150
-
151
- ```tsx
152
- import { MonacoEditor } from './monaco-editor'
153
-
154
- function CodePanel({ code }: { code: string }) {
155
- return <MonacoEditor value={code} />
156
- }
157
- ```
158
-
159
- **Correct (Monaco loads on demand):**
160
-
161
- ```tsx
162
- import dynamic from 'next/dynamic'
163
-
164
- const MonacoEditor = dynamic(
165
- () => import('./monaco-editor').then(m => m.MonacoEditor),
166
- { ssr: false }
167
- )
168
-
169
- function CodePanel({ code }: { code: string }) {
170
- return <MonacoEditor value={code} />
171
- }
172
- ```
173
-
174
- ### 2.2 Preload Based on User Intent
175
-
176
- Preload heavy bundles before they're needed to reduce perceived
177
- latency.
178
-
179
- **Example (preload on hover/focus):**
180
-
181
- ```tsx
182
- function EditorButton({ onClick }: { onClick: () => void }) {
183
- const preload = () => {
184
- void import('./monaco-editor')
185
- }
186
-
187
- return (
188
- <button
189
- onMouseEnter={preload}
190
- onFocus={preload}
191
- onClick={onClick}
192
- >
193
- Open Editor
194
- </button>
195
- )
196
- }
197
- ```
198
-
199
- ### 2.3 Conditional Module Loading
200
-
201
- Load large data or modules only when a feature is activated.
202
-
203
- **Example (lazy-load animation frames):**
204
-
205
- ```tsx
206
- function AnimationPlayer({ enabled }: { enabled: boolean }) {
207
- const [frames, setFrames] = useState<Frame[] | null>(null)
208
-
209
- useEffect(() => {
210
- if (enabled && !frames) {
211
- import('./animation-frames.js')
212
- .then(mod => setFrames(mod.frames))
213
- .catch(() => setEnabled(false))
214
- }
215
- }, [enabled, frames])
216
-
217
- if (!frames) return <Skeleton />
218
- return <Canvas frames={frames} />
219
- }
220
- ```
221
-
222
- ### 2.4 Defer Non-Critical Third-Party Libraries
223
-
224
- Analytics, logging, and error tracking don't block user
225
- interaction. Load them after hydration.
226
-
227
- **Incorrect (blocks initial bundle):**
228
-
229
- ```tsx
230
- import { Analytics } from '@vercel/analytics/react'
231
-
232
- export default function RootLayout({ children }) {
233
- return (
234
- <html>
235
- <body>
236
- {children}
237
- <Analytics />
238
- </body>
239
- </html>
240
- )
241
- }
242
- ```
243
-
244
- **Correct (loads after hydration):**
245
-
246
- ```tsx
247
- import dynamic from 'next/dynamic'
248
-
249
- const Analytics = dynamic(
250
- () => import('@vercel/analytics/react').then(m => m.Analytics),
251
- { ssr: false }
252
- )
253
-
254
- export default function RootLayout({ children }) {
255
- return (
256
- <html>
257
- <body>
258
- {children}
259
- <Analytics />
260
- </body>
261
- </html>
262
- )
263
- }
264
- ```
265
-
266
- ---
267
-
268
- ## 3. Server-Side Performance
269
-
270
- **Impact: HIGH (eliminates server-side waterfalls)**
271
-
272
- ### 3.1 Parallel Data Fetching with Component Composition
273
-
274
- React Server Components execute sequentially within a tree.
275
- Restructure with composition to parallelize data fetching.
276
-
277
- **Incorrect (Sidebar waits for Page's fetch to complete):**
278
-
279
- ```tsx
280
- export default async function Page() {
281
- const header = await fetchHeader()
282
- return (
283
- <div>
284
- <div>{header}</div>
285
- <Sidebar />
286
- </div>
287
- )
288
- }
289
-
290
- async function Sidebar() {
291
- const items = await fetchSidebarItems()
292
- return <nav>{items.map(renderItem)}</nav>
293
- }
294
- ```
295
-
296
- **Correct (both fetch simultaneously):**
297
-
298
- ```tsx
299
- async function Header() {
300
- const data = await fetchHeader()
301
- return <div>{data}</div>
302
- }
303
-
304
- async function Sidebar() {
305
- const items = await fetchSidebarItems()
306
- return <nav>{items.map(renderItem)}</nav>
307
- }
308
-
309
- export default function Page() {
310
- return (
311
- <div>
312
- <Header />
313
- <Sidebar />
314
- </div>
315
- )
316
- }
317
- ```
318
-
319
- ### 3.2 Minimize Serialization at RSC Boundaries
320
-
321
- The React Server/Client boundary serializes all object
322
- properties. Only pass fields that the client actually uses.
323
-
324
- **Incorrect (serializes all 50 fields):**
325
-
326
- ```tsx
327
- async function Page() {
328
- const user = await fetchUser() // 50 fields
329
- return <Profile user={user} />
330
- }
331
-
332
- 'use client'
333
- function Profile({ user }: { user: User }) {
334
- return <div>{user.name}</div> // uses 1 field
335
- }
336
- ```
337
-
338
- **Correct (serializes only 1 field):**
339
-
340
- ```tsx
341
- async function Page() {
342
- const user = await fetchUser()
343
- return <Profile name={user.name} />
344
- }
345
-
346
- 'use client'
347
- function Profile({ name }: { name: string }) {
348
- return <div>{name}</div>
349
- }
350
- ```
351
-
352
- ### 3.3 Per-Request Deduplication with React.cache()
353
-
354
- Use `React.cache()` for server-side request deduplication.
355
- Authentication and database queries benefit most.
356
-
357
- **Usage:**
358
-
359
- ```typescript
360
- import { cache } from 'react'
361
-
362
- export const getCurrentUser = cache(async () => {
363
- const session = await auth()
364
- if (!session?.user?.id) return null
365
- return await db.user.findUnique({
366
- where: { id: session.user.id }
367
- })
368
- })
369
- ```
370
-
371
- Within a single request, multiple calls to `getCurrentUser()`
372
- execute the query only once.
373
-
374
- ### 3.4 Cross-Request LRU Caching
375
-
376
- `React.cache()` only works within one request. For data shared
377
- across sequential requests (user clicks button A then button B),
378
- use an LRU cache.
379
-
380
- **Implementation:**
381
-
382
- ```typescript
383
- import { LRUCache } from 'lru-cache'
384
-
385
- const cache = new LRUCache<string, any>({
386
- max: 1000,
387
- ttl: 5 * 60 * 1000 // 5 minutes
388
- })
389
-
390
- export async function getUser(id: string) {
391
- const cached = cache.get(id)
392
- if (cached) return cached
393
-
394
- const user = await db.user.findUnique({ where: { id } })
395
- cache.set(id, user)
396
- return user
397
- }
398
- ```
399
-
400
- Use when sequential user actions hit multiple endpoints needing
401
- the same data within seconds. In serverless, consider Redis for
402
- cross-process caching.
403
-
404
- ---
405
-
406
- ## 4. Client-Side Data Fetching
407
-
408
- **Impact: MEDIUM-HIGH (automatic deduplication)**
409
-
410
- ### 4.1 Use SWR for Automatic Deduplication
411
-
412
- SWR enables request deduplication, caching, and revalidation
413
- across component instances.
414
-
415
- **Incorrect (no deduplication, each instance fetches):**
416
-
417
- ```tsx
418
- function UserList() {
419
- const [users, setUsers] = useState([])
420
- useEffect(() => {
421
- fetch('/api/users')
422
- .then(r => r.json())
423
- .then(setUsers)
424
- }, [])
425
- }
426
- ```
427
-
428
- **Correct (multiple instances share one request):**
429
-
430
- ```tsx
431
- import useSWR from 'swr'
432
-
433
- function UserList() {
434
- const { data: users } = useSWR('/api/users', fetcher)
435
- }
436
- ```
437
-
438
- ### 4.2 Deduplicate Global Event Listeners
439
-
440
- Use `useSWRSubscription()` to share global event listeners
441
- across component instances.
442
-
443
- **Incorrect (N instances = N listeners):**
444
-
445
- ```tsx
446
- function KeyboardShortcut({ onTrigger }: Props) {
447
- useEffect(() => {
448
- const handler = (e: KeyboardEvent) => {
449
- if (e.metaKey && e.key === 'k') {
450
- onTrigger()
451
- }
452
- }
453
- window.addEventListener('keydown', handler)
454
- return () => window.removeEventListener('keydown', handler)
455
- }, [onTrigger])
456
- }
457
- ```
458
-
459
- **Correct (N instances = 1 listener):**
460
-
461
- ```tsx
462
- import useSWRSubscription from 'swr/subscription'
463
-
464
- function useKeyboardShortcut(key: string, callback: () => void) {
465
- useSWRSubscription(['keydown', key], (_, { next }) => {
466
- const handler = (e: KeyboardEvent) => {
467
- if (e.metaKey && e.key === key) {
468
- next(null, e)
469
- callback()
470
- }
471
- }
472
- window.addEventListener('keydown', handler)
473
- return () => window.removeEventListener('keydown', handler)
474
- })
475
- }
476
- ```
477
-
478
- ---
479
-
480
- ## 5. Re-render Optimization
481
-
482
- **Impact: MEDIUM (reduces unnecessary work)**
483
-
484
- ### 5.1 Subscribe to Derived State
485
-
486
- Subscribe to derived boolean state instead of continuous values
487
- to reduce re-render frequency.
488
-
489
- **Incorrect (re-renders on every pixel change):**
490
-
491
- ```tsx
492
- function Sidebar() {
493
- const width = useWindowWidth() // updates continuously
494
- const isMobile = width < 768
495
- return <nav className={isMobile ? 'mobile' : 'desktop'}>
496
- }
497
- ```
498
-
499
- **Correct (re-renders only when boolean changes):**
500
-
501
- ```tsx
502
- function Sidebar() {
503
- const isMobile = useMediaQuery('(max-width: 767px)')
504
- return <nav className={isMobile ? 'mobile' : 'desktop'}>
505
- }
506
- ```
507
-
508
- ### 5.2 Narrow Effect Dependencies
509
-
510
- Specify primitive dependencies instead of objects to minimize
511
- effect re-runs.
512
-
513
- **Incorrect (re-runs on any user field change):**
514
-
515
- ```tsx
516
- useEffect(() => {
517
- console.log(user.id)
518
- }, [user])
519
- ```
520
-
521
- **Correct (re-runs only when id changes):**
522
-
523
- ```tsx
524
- useEffect(() => {
525
- console.log(user.id)
526
- }, [user.id])
527
- ```
528
-
529
- ### 5.3 Use Transitions for Non-Urgent Updates
530
-
531
- Mark frequent, non-urgent state updates as transitions to
532
- maintain UI responsiveness.
533
-
534
- **Incorrect (blocks UI on every scroll):**
535
-
536
- ```tsx
537
- function ScrollTracker() {
538
- const [scrollY, setScrollY] = useState(0)
539
- useEffect(() => {
540
- const handler = () => setScrollY(window.scrollY)
541
- window.addEventListener('scroll', handler, { passive: true })
542
- return () => window.removeEventListener('scroll', handler)
543
- }, [])
544
- }
545
- ```
546
-
547
- **Correct (non-blocking updates):**
548
-
549
- ```tsx
550
- import { startTransition } from 'react'
551
-
552
- function ScrollTracker() {
553
- const [scrollY, setScrollY] = useState(0)
554
- useEffect(() => {
555
- const handler = () => {
556
- startTransition(() => setScrollY(window.scrollY))
557
- }
558
- window.addEventListener('scroll', handler, { passive: true })
559
- return () => window.removeEventListener('scroll', handler)
560
- }, [])
561
- }
562
- ```
563
-
564
- ### 5.4 Extract to Memoized Components
565
-
566
- Extract expensive work into memoized components to enable early
567
- returns before computation.
568
-
569
- **Incorrect (computes avatar even when loading):**
570
-
571
- ```tsx
572
- function Profile({ user, loading }: Props) {
573
- const avatar = useMemo(() => {
574
- const id = computeAvatarId(user)
575
- return <Avatar id={id} />
576
- }, [user])
577
-
578
- if (loading) return <Skeleton />
579
- return <div>{avatar}</div>
580
- }
581
- ```
582
-
583
- **Correct (skips computation when loading):**
584
-
585
- ```tsx
586
- const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
587
- const id = useMemo(() => computeAvatarId(user), [user])
588
- return <Avatar id={id} />
589
- })
590
-
591
- function Profile({ user, loading }: Props) {
592
- if (loading) return <Skeleton />
593
- return (
594
- <div>
595
- <UserAvatar user={user} />
596
- </div>
597
- )
598
- }
599
- ```
600
-
601
- ### 5.5 Defer State Reads to Usage Point
602
-
603
- Don't subscribe to dynamic state (searchParams, localStorage)
604
- if you only read it inside callbacks.
605
-
606
- **Incorrect (subscribes to all searchParams changes):**
607
-
608
- ```tsx
609
- function ShareButton({ chatId }: { chatId: string }) {
610
- const searchParams = useSearchParams()
611
-
612
- const handleShare = () => {
613
- const ref = searchParams.get('ref')
614
- shareChat(chatId, { ref })
615
- }
616
-
617
- return <button onClick={handleShare}>Share</button>
618
- }
619
- ```
620
-
621
- **Correct (reads on demand, no subscription):**
622
-
623
- ```tsx
624
- function ShareButton({ chatId }: { chatId: string }) {
625
- const handleShare = () => {
626
- const params = new URLSearchParams(window.location.search)
627
- const ref = params.get('ref')
628
- shareChat(chatId, { ref })
629
- }
630
-
631
- return <button onClick={handleShare}>Share</button>
632
- }
633
- ```
634
-
635
- ---
636
-
637
- ## 6. Rendering Performance
638
-
639
- **Impact: MEDIUM (reduces rendering work)**
640
-
641
- ### 6.1 Hoist Static JSX Elements
642
-
643
- Extract static JSX outside components to avoid re-creation.
644
-
645
- **Incorrect (recreates element every render):**
646
-
647
- ```tsx
648
- function LoadingSkeleton() {
649
- return <div className="animate-pulse h-20 bg-gray-200" />
650
- }
651
-
652
- function Container() {
653
- return (
654
- <div>
655
- {loading && <LoadingSkeleton />}
656
- </div>
657
- )
658
- }
659
- ```
660
-
661
- **Correct (reuses same element):**
662
-
663
- ```tsx
664
- const loadingSkeleton = (
665
- <div className="animate-pulse h-20 bg-gray-200" />
666
- )
667
-
668
- function Container() {
669
- return (
670
- <div>
671
- {loading && loadingSkeleton}
672
- </div>
673
- )
674
- }
675
- ```
676
-
677
- ### 6.2 Use Activity Component for Show/Hide
678
-
679
- Use React's `<Activity>` to preserve state/DOM for expensive
680
- components that frequently toggle visibility.
681
-
682
- **Usage:**
683
-
684
- ```tsx
685
- import { Activity } from 'react'
686
-
687
- function Dropdown({ isOpen }: Props) {
688
- return (
689
- <Activity mode={isOpen ? 'visible' : 'hidden'}>
690
- <ExpensiveMenu />
691
- </Activity>
692
- )
693
- }
694
- ```
695
-
696
- ### 6.3 CSS content-visibility for Long Lists
697
-
698
- Apply `content-visibility: auto` to defer off-screen rendering.
699
-
700
- **CSS:**
701
-
702
- ```css
703
- .message-item {
704
- content-visibility: auto;
705
- contain-intrinsic-size: 0 80px;
706
- }
707
- ```
708
-
709
- **Example:**
710
-
711
- ```tsx
712
- function MessageList({ messages }: { messages: Message[] }) {
713
- return (
714
- <div className="overflow-y-auto h-screen">
715
- {messages.map(msg => (
716
- <div key={msg.id} className="message-item">
717
- <Avatar user={msg.author} />
718
- <div>{msg.content}</div>
719
- </div>
720
- ))}
721
- </div>
722
- )
723
- }
724
- ```
725
-
726
- For 1000 messages, browser skips layout/paint for ~990
727
- off-screen items (10x faster initial render).
728
-
729
- ### 6.4 Optimize SVG Precision
730
-
731
- Reduce SVG coordinate precision to decrease file size.
732
-
733
- **Incorrect (excessive precision):**
734
-
735
- ```svg
736
- <path d="M 10.293847 20.847362 L 30.938472 40.192837" />
737
- ```
738
-
739
- **Correct (1 decimal place):**
740
-
741
- ```svg
742
- <path d="M 10.3 20.8 L 30.9 40.2" />
743
- ```
744
-
745
- **Automate with SVGO:**
746
-
747
- ```bash
748
- npx svgo --precision=1 --multipass icon.svg
749
- ```
750
-
751
- ---
752
-
753
- ## 7. JavaScript Performance
754
-
755
- **Impact: LOW-MEDIUM (micro-optimizations for hot paths)**
756
-
757
- ### 7.1 Combine Multiple Array Iterations
758
-
759
- Multiple `.filter()` or `.map()` calls iterate the array
760
- multiple times. Combine into one loop.
761
-
762
- **Incorrect (3 iterations):**
763
-
764
- ```typescript
765
- const admins = users.filter(u => u.isAdmin)
766
- const testers = users.filter(u => u.isTester)
767
- const inactive = users.filter(u => !u.isActive)
768
- ```
769
-
770
- **Correct (1 iteration):**
771
-
772
- ```typescript
773
- const admins: User[] = []
774
- const testers: User[] = []
775
- const inactive: User[] = []
776
-
777
- for (const user of users) {
778
- if (user.isAdmin) admins.push(user)
779
- if (user.isTester) testers.push(user)
780
- if (!user.isActive) inactive.push(user)
781
- }
782
- ```
783
-
784
- ### 7.2 Cache Property Access in Loops
785
-
786
- Cache object property lookups in hot paths.
787
-
788
- **Incorrect (3 lookups x N iterations):**
789
-
790
- ```typescript
791
- for (let i = 0; i < arr.length; i++) {
792
- process(obj.config.settings.value)
793
- }
794
- ```
795
-
796
- **Correct (1 lookup total):**
797
-
798
- ```typescript
799
- const value = obj.config.settings.value
800
- const len = arr.length
801
- for (let i = 0; i < len; i++) {
802
- process(value)
803
- }
804
- ```
805
-
806
- ### 7.3 Use Set/Map for O(1) Lookups
807
-
808
- Convert arrays to Set/Map for repeated membership checks.
809
-
810
- **Incorrect (O(n) per check):**
811
-
812
- ```typescript
813
- const allowedIds = ['a', 'b', 'c', ...]
814
- items.filter(item => allowedIds.includes(item.id))
815
- ```
816
-
817
- **Correct (O(1) per check):**
818
-
819
- ```typescript
820
- const allowedIds = new Set(['a', 'b', 'c', ...])
821
- items.filter(item => allowedIds.has(item.id))
822
- ```
823
-
824
- ### 7.4 Early Exit from Loops
825
-
826
- Exit as soon as result is determined.
827
-
828
- **Incorrect (always iterates all):**
829
-
830
- ```typescript
831
- const hasAdmin = users.some(u => u.role === 'admin')
832
- const admin = users.find(u => u.role === 'admin')
833
- // Two iterations
834
- ```
835
-
836
- **Correct (single pass with early exit):**
837
-
838
- ```typescript
839
- let admin: User | undefined
840
- for (const user of users) {
841
- if (user.role === 'admin') {
842
- admin = user
843
- break
844
- }
845
- }
846
- const hasAdmin = admin !== undefined
847
- ```
848
-
849
- ### 7.5 Hoist RegExp Creation
850
-
851
- Don't create RegExp inside render. Hoist to module scope or
852
- memoize with `useMemo()`.
853
-
854
- **Incorrect (new RegExp every render):**
855
-
856
- ```tsx
857
- function Highlighter({ text, query }: Props) {
858
- const regex = new RegExp(`(${query})`, 'gi')
859
- const parts = text.split(regex)
860
- return <>{parts.map((part, i) => ...)}</>
861
- }
862
- ```
863
-
864
- **Correct (memoize or hoist):**
865
-
866
- ```tsx
867
- const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
868
-
869
- function Highlighter({ text, query }: Props) {
870
- const regex = useMemo(
871
- () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
872
- [query]
873
- )
874
- const parts = text.split(regex)
875
- return <>{parts.map((part, i) => ...)}</>
876
- }
877
- ```
878
-
879
- ### 7.6 Cache Storage API Calls
880
-
881
- `localStorage`, `sessionStorage`, and `document.cookie` are
882
- synchronous and expensive. Cache reads in memory.
883
-
884
- **Incorrect (reads storage on every call):**
885
-
886
- ```typescript
887
- function getTheme() {
888
- return localStorage.getItem('theme') ?? 'light'
889
- }
890
- // Called 10 times = 10 storage reads
891
- ```
892
-
893
- **Correct (Map cache):**
894
-
895
- ```typescript
896
- const storageCache = new Map<string, string | null>()
897
-
898
- function getLocalStorage(key: string) {
899
- if (!storageCache.has(key)) {
900
- storageCache.set(key, localStorage.getItem(key))
901
- }
902
- return storageCache.get(key)
903
- }
904
-
905
- function setLocalStorage(key: string, value: string) {
906
- localStorage.setItem(key, value)
907
- storageCache.set(key, value) // keep cache in sync
908
- }
909
- ```
910
-
911
- ### 7.7 Build Index Maps for Repeated Lookups
912
-
913
- Multiple `.find()` calls by the same key should use a Map.
914
-
915
- **Incorrect (O(n) per lookup):**
916
-
917
- ```typescript
918
- function processOrders(orders: Order[], users: User[]) {
919
- return orders.map(order => ({
920
- ...order,
921
- user: users.find(u => u.id === order.userId)
922
- }))
923
- }
924
- ```
925
-
926
- **Correct (O(1) per lookup):**
927
-
928
- ```typescript
929
- function processOrders(orders: Order[], users: User[]) {
930
- const userById = new Map(users.map(u => [u.id, u]))
931
-
932
- return orders.map(order => ({
933
- ...order,
934
- user: userById.get(order.userId)
935
- }))
936
- }
937
- ```
938
-
939
- Build map once (O(n)), then all lookups are O(1).
940
- For 1000 orders x 1000 users: 1M ops -> 2K ops.
941
-
942
- ---
943
-
944
- ## 8. Advanced Patterns
945
-
946
- **Impact: LOW (advanced patterns for specific cases)**
947
-
948
- ### 8.1 useLatest for Stable Callback Refs
949
-
950
- Access latest values in callbacks without adding them to
951
- dependency arrays. Prevents effect re-runs while avoiding
952
- stale closures.
953
-
954
- **Implementation:**
955
-
956
- ```typescript
957
- function useLatest<T>(value: T) {
958
- const ref = useRef(value)
959
- useEffect(() => {
960
- ref.current = value
961
- }, [value])
962
- return ref
963
- }
964
- ```
965
-
966
- **Incorrect (effect re-runs on every callback change):**
967
-
968
- ```tsx
969
- function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
970
- const [query, setQuery] = useState('')
971
-
972
- useEffect(() => {
973
- const timeout = setTimeout(() => onSearch(query), 300)
974
- return () => clearTimeout(timeout)
975
- }, [query, onSearch])
976
- }
977
- ```
978
-
979
- **Correct (stable effect, fresh callback):**
980
-
981
- ```tsx
982
- function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
983
- const [query, setQuery] = useState('')
984
- const onSearchRef = useLatest(onSearch)
985
-
986
- useEffect(() => {
987
- const timeout = setTimeout(() => onSearchRef.current(query), 300)
988
- return () => clearTimeout(timeout)
989
- }, [query])
990
- }
991
- ```
992
-
993
- ### 8.2 Store Event Handlers in Refs
994
-
995
- Store callbacks in refs when used in effects that shouldn't
996
- re-subscribe on callback changes.
997
-
998
- **Incorrect (re-subscribes on every render):**
999
-
1000
- ```tsx
1001
- function useWindowEvent(event: string, handler: () => void) {
1002
- useEffect(() => {
1003
- window.addEventListener(event, handler)
1004
- return () => window.removeEventListener(event, handler)
1005
- }, [event, handler])
1006
- }
1007
- ```
1008
-
1009
- **Correct (stable subscription):**
1010
-
1011
- ```tsx
1012
- function useWindowEvent(event: string, handler: () => void) {
1013
- const handlerRef = useRef(handler)
1014
- useEffect(() => {
1015
- handlerRef.current = handler
1016
- }, [handler])
1017
-
1018
- useEffect(() => {
1019
- const listener = () => handlerRef.current()
1020
- window.addEventListener(event, listener)
1021
- return () => window.removeEventListener(event, listener)
1022
- }, [event])
1023
- }
1024
- ```
1025
-
1026
- ---
1027
-
1028
- ## References
1029
-
1030
- 1. [React Documentation](https://react.dev)
1031
- 2. [Next.js Documentation](https://nextjs.org)
1032
- 3. [SWR Documentation](https://swr.vercel.app)
1033
- 4. [better-all](https://github.com/shuding/better-all)
1034
- 5. [LRU Cache](https://github.com/isaacs/node-lru-cache)