picasso-skill 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,746 @@
1
+ # Performance Optimization Reference
2
+
3
+ React/Next.js performance rules based on Vercel's guidance. Organized by priority. Every pattern includes the why, the fix, and working code.
4
+
5
+ ---
6
+
7
+ ## Priority 1 - CRITICAL: Eliminating Waterfalls
8
+
9
+ Waterfalls are the single biggest performance killer. Every sequential `await` that could be parallel is wasted time.
10
+
11
+ ### Promise.all() for Independent Operations
12
+
13
+ ```typescript
14
+ // BAD: Sequential - total time = A + B + C
15
+ async function getPageData(userId: string) {
16
+ const user = await fetchUser(userId);
17
+ const posts = await fetchPosts(userId);
18
+ const notifications = await fetchNotifications(userId);
19
+ return { user, posts, notifications };
20
+ }
21
+
22
+ // GOOD: Parallel - total time = max(A, B, C)
23
+ async function getPageData(userId: string) {
24
+ const [user, posts, notifications] = await Promise.all([
25
+ fetchUser(userId),
26
+ fetchPosts(userId),
27
+ fetchNotifications(userId),
28
+ ]);
29
+ return { user, posts, notifications };
30
+ }
31
+ ```
32
+
33
+ ### Defer Await Until Needed
34
+
35
+ Don't block code branches that don't use the data.
36
+
37
+ ```typescript
38
+ // BAD: Blocks even if we early-return
39
+ async function handleRequest(req: Request) {
40
+ const config = await fetchConfig();
41
+ const user = await getUser(req);
42
+
43
+ if (!user) {
44
+ return redirect('/login'); // config was fetched for nothing
45
+ }
46
+
47
+ return renderPage(user, config);
48
+ }
49
+
50
+ // GOOD: Start fetch immediately, await only when needed
51
+ async function handleRequest(req: Request) {
52
+ const configPromise = fetchConfig(); // fire immediately, don't await
53
+ const user = await getUser(req);
54
+
55
+ if (!user) {
56
+ return redirect('/login'); // config fetch may still be in-flight, that's fine
57
+ }
58
+
59
+ const config = await configPromise; // await only when we need it
60
+ return renderPage(user, config);
61
+ }
62
+ ```
63
+
64
+ ### Dependency-Based Parallelization with better-all
65
+
66
+ When some fetches depend on others but you still want maximum parallelism:
67
+
68
+ ```typescript
69
+ import { all } from 'better-all';
70
+
71
+ // Runs A and B in parallel. C starts as soon as A finishes (doesn't wait for B).
72
+ const { user, posts, comments } = await all({
73
+ user: () => fetchUser(id),
74
+ posts: () => fetchPosts(id),
75
+ comments: async ({ user }) => fetchComments(user.teamId), // depends on user
76
+ });
77
+ ```
78
+
79
+ ### Strategic Suspense Boundaries
80
+
81
+ Wrap independent data-loading sections in their own Suspense boundary so they stream independently.
82
+
83
+ ```tsx
84
+ // BAD: One boundary = entire page waits for slowest component
85
+ <Suspense fallback={<PageSkeleton />}>
86
+ <Header /> {/* 50ms */}
87
+ <Feed /> {/* 2000ms - blocks everything */}
88
+ <Sidebar /> {/* 100ms */}
89
+ </Suspense>
90
+
91
+ // GOOD: Independent boundaries = each streams when ready
92
+ <Header />
93
+ <Suspense fallback={<FeedSkeleton />}>
94
+ <Feed />
95
+ </Suspense>
96
+ <Suspense fallback={<SidebarSkeleton />}>
97
+ <Sidebar />
98
+ </Suspense>
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Priority 2 - CRITICAL: Bundle Size
104
+
105
+ ### Avoid Barrel File Imports
106
+
107
+ Barrel files (`index.ts` re-exports) pull in entire modules. Cost: 200-800ms of parse time.
108
+
109
+ ```typescript
110
+ // BAD: Imports entire icon library through barrel file
111
+ import { ChevronDown } from '@/components/icons';
112
+ // This imports index.ts which re-exports 500 icons
113
+
114
+ // GOOD: Direct import from source file
115
+ import { ChevronDown } from '@/components/icons/ChevronDown';
116
+
117
+ // BAD: Barrel file for utils
118
+ import { formatDate } from '@/lib/utils';
119
+ // Pulls in every util function
120
+
121
+ // GOOD: Direct import
122
+ import { formatDate } from '@/lib/utils/formatDate';
123
+ ```
124
+
125
+ Configure your bundler to detect this:
126
+
127
+ ```javascript
128
+ // next.config.js
129
+ module.exports = {
130
+ experimental: {
131
+ optimizePackageImports: ['@/components/icons', 'lucide-react', 'date-fns'],
132
+ },
133
+ };
134
+ ```
135
+
136
+ ### Dynamic Imports for Heavy Components
137
+
138
+ ```typescript
139
+ import dynamic from 'next/dynamic';
140
+
141
+ // Heavy editor component - only loads when rendered
142
+ const CodeEditor = dynamic(() => import('@/components/CodeEditor'), {
143
+ loading: () => <EditorSkeleton />,
144
+ });
145
+
146
+ // Chart library - loads on demand
147
+ const Chart = dynamic(() => import('@/components/Chart'), {
148
+ loading: () => <ChartSkeleton />,
149
+ ssr: false, // Skip server rendering for client-only libs
150
+ });
151
+ ```
152
+
153
+ ### Defer Non-Critical Third-Party Scripts
154
+
155
+ ```typescript
156
+ // Analytics, error tracking, chat widgets - none are needed for first render
157
+ const Analytics = dynamic(() => import('@/components/Analytics'), {
158
+ ssr: false,
159
+ });
160
+ const ErrorTracker = dynamic(() => import('@/components/ErrorTracker'), {
161
+ ssr: false,
162
+ });
163
+
164
+ // Load after page is interactive
165
+ export default function Layout({ children }) {
166
+ return (
167
+ <>
168
+ {children}
169
+ <Suspense fallback={null}>
170
+ <Analytics />
171
+ <ErrorTracker />
172
+ </Suspense>
173
+ </>
174
+ );
175
+ }
176
+ ```
177
+
178
+ ### Preload on User Intent
179
+
180
+ Start loading a route or component when the user shows intent (hover, focus) instead of on click.
181
+
182
+ ```tsx
183
+ import { useRouter } from 'next/navigation';
184
+
185
+ function NavLink({ href, children }) {
186
+ const router = useRouter();
187
+
188
+ return (
189
+ <Link
190
+ href={href}
191
+ onMouseEnter={() => router.prefetch(href)}
192
+ onFocus={() => router.prefetch(href)}
193
+ >
194
+ {children}
195
+ </Link>
196
+ );
197
+ }
198
+ ```
199
+
200
+ For heavy components:
201
+
202
+ ```typescript
203
+ // Preload the module on hover, render on click
204
+ const importEditor = () => import('@/components/CodeEditor');
205
+ const CodeEditor = dynamic(importEditor);
206
+
207
+ function EditorButton() {
208
+ const [show, setShow] = useState(false);
209
+
210
+ return (
211
+ <>
212
+ <button
213
+ onMouseEnter={() => importEditor()} // preload on hover
214
+ onClick={() => setShow(true)}
215
+ >
216
+ Open Editor
217
+ </button>
218
+ {show && <CodeEditor />}
219
+ </>
220
+ );
221
+ }
222
+ ```
223
+
224
+ ### Conditional Module Loading
225
+
226
+ Only load modules when the feature is actually used.
227
+
228
+ ```typescript
229
+ async function exportData(format: 'csv' | 'xlsx') {
230
+ if (format === 'xlsx') {
231
+ // xlsx library is 200KB+ - only load when needed
232
+ const XLSX = await import('xlsx');
233
+ return XLSX.utils.json_to_sheet(data);
234
+ }
235
+ // CSV is trivial, no import needed
236
+ return data.map(row => row.join(',')).join('\n');
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Priority 3 - HIGH: Server-Side Performance
243
+
244
+ ### React.cache() for Per-Request Deduplication
245
+
246
+ Multiple components in the same render can call the same function. `React.cache()` ensures it only executes once per request.
247
+
248
+ ```typescript
249
+ import { cache } from 'react';
250
+
251
+ export const getUser = cache(async (userId: string) => {
252
+ const res = await fetch(`/api/users/${userId}`);
253
+ return res.json();
254
+ });
255
+
256
+ // Component A calls getUser('123') - makes network request
257
+ // Component B calls getUser('123') - returns cached result from same request
258
+ // Next request: cache is cleared, fresh fetch
259
+ ```
260
+
261
+ ### LRU Cache for Cross-Request Caching
262
+
263
+ For data that doesn't change per-request (config, feature flags, static content):
264
+
265
+ ```typescript
266
+ import { LRUCache } from 'lru-cache';
267
+
268
+ const cache = new LRUCache<string, any>({
269
+ max: 500, // max entries
270
+ ttl: 1000 * 60 * 5, // 5 minute TTL
271
+ });
272
+
273
+ export async function getConfig(key: string) {
274
+ const cached = cache.get(key);
275
+ if (cached) return cached;
276
+
277
+ const config = await db.config.findUnique({ where: { key } });
278
+ cache.set(key, config);
279
+ return config;
280
+ }
281
+ ```
282
+
283
+ ### Parallel Data Fetching with Component Composition
284
+
285
+ Let each Server Component fetch its own data. React deduplicates and parallelizes automatically.
286
+
287
+ ```tsx
288
+ // page.tsx - Don't fetch everything here and pass down
289
+ export default function DashboardPage() {
290
+ return (
291
+ <div>
292
+ <UserHeader /> {/* fetches user data */}
293
+ <StatsPanel /> {/* fetches stats data */}
294
+ <ActivityFeed /> {/* fetches activity data */}
295
+ </div>
296
+ );
297
+ }
298
+
299
+ // Each component is independent - React runs them in parallel
300
+ async function UserHeader() {
301
+ const user = await getUser(); // deduplicated with React.cache
302
+ return <header>{user.name}</header>;
303
+ }
304
+
305
+ async function StatsPanel() {
306
+ const stats = await getStats();
307
+ return <div>{stats.total}</div>;
308
+ }
309
+ ```
310
+
311
+ ### Minimize Serialization at RSC Boundaries
312
+
313
+ Only pass serializable, minimal data from Server to Client Components.
314
+
315
+ ```tsx
316
+ // BAD: Passing entire user object (large, may have non-serializable fields)
317
+ <ClientComponent user={fullUserObject} />
318
+
319
+ // GOOD: Pass only what the client needs
320
+ <ClientComponent userName={user.name} userAvatar={user.avatarUrl} />
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Priority 4 - MEDIUM-HIGH: Client-Side Data
326
+
327
+ ### SWR for Deduplication, Caching, and Revalidation
328
+
329
+ ```typescript
330
+ import useSWR from 'swr';
331
+
332
+ const fetcher = (url: string) => fetch(url).then(r => r.json());
333
+
334
+ function useUser(id: string) {
335
+ const { data, error, isLoading, mutate } = useSWR(
336
+ `/api/users/${id}`,
337
+ fetcher,
338
+ {
339
+ revalidateOnFocus: false, // Don't refetch on tab focus
340
+ dedupingInterval: 5000, // Deduplicate requests within 5s
341
+ staleWhileRevalidate: true, // Show stale data while fetching fresh
342
+ }
343
+ );
344
+
345
+ return { user: data, error, isLoading, mutate };
346
+ }
347
+
348
+ // Multiple components calling useUser('123') = ONE network request
349
+ ```
350
+
351
+ ### Deduplicate Global Event Listeners
352
+
353
+ ```typescript
354
+ // BAD: Every component instance adds its own listener
355
+ function Component() {
356
+ useEffect(() => {
357
+ const handler = () => { /* ... */ };
358
+ window.addEventListener('resize', handler);
359
+ return () => window.removeEventListener('resize', handler);
360
+ }, []);
361
+ }
362
+
363
+ // GOOD: Single shared listener, components subscribe to derived state
364
+ import { useSyncExternalStore } from 'react';
365
+
366
+ let width = window.innerWidth;
367
+ const listeners = new Set<() => void>();
368
+
369
+ window.addEventListener('resize', () => {
370
+ width = window.innerWidth;
371
+ listeners.forEach(l => l());
372
+ });
373
+
374
+ function subscribe(listener: () => void) {
375
+ listeners.add(listener);
376
+ return () => listeners.delete(listener);
377
+ }
378
+
379
+ export function useWindowWidth() {
380
+ return useSyncExternalStore(subscribe, () => width);
381
+ }
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Priority 5 - MEDIUM: Re-render Optimization
387
+
388
+ ### Defer State Reads to Usage Point
389
+
390
+ ```tsx
391
+ // BAD: Parent re-renders on every keystroke, all children re-render
392
+ function SearchPage() {
393
+ const [query, setQuery] = useState('');
394
+ return (
395
+ <div>
396
+ <SearchInput value={query} onChange={setQuery} />
397
+ <ExpensiveHeader /> {/* Re-renders on every keystroke! */}
398
+ <SearchResults query={query} />
399
+ </div>
400
+ );
401
+ }
402
+
403
+ // GOOD: Isolate state to the component that needs it
404
+ function SearchPage() {
405
+ return (
406
+ <div>
407
+ <SearchSection /> {/* Contains its own state */}
408
+ <ExpensiveHeader /> {/* Never re-renders from search */}
409
+ </div>
410
+ );
411
+ }
412
+
413
+ function SearchSection() {
414
+ const [query, setQuery] = useState('');
415
+ return (
416
+ <>
417
+ <SearchInput value={query} onChange={setQuery} />
418
+ <SearchResults query={query} />
419
+ </>
420
+ );
421
+ }
422
+ ```
423
+
424
+ ### Narrow Effect Dependencies
425
+
426
+ ```typescript
427
+ // BAD: Effect runs whenever any user field changes
428
+ useEffect(() => {
429
+ trackPageView(user.id);
430
+ }, [user]); // user is an object, new reference every render
431
+
432
+ // GOOD: Depend on the primitive you actually use
433
+ const userId = user.id;
434
+ useEffect(() => {
435
+ trackPageView(userId);
436
+ }, [userId]); // Only runs when the ID actually changes
437
+ ```
438
+
439
+ ### Subscribe to Derived State
440
+
441
+ ```typescript
442
+ // BAD: Re-renders on every pixel of resize
443
+ function Component() {
444
+ const width = useWindowWidth(); // 1024, 1023, 1022...
445
+ const isMobile = width < 768;
446
+ // Renders on every width change even if isMobile doesn't change
447
+ }
448
+
449
+ // GOOD: Only re-renders when the boolean flips
450
+ function Component() {
451
+ const isMobile = useSyncExternalStore(
452
+ subscribe,
453
+ () => window.innerWidth < 768 // returns boolean, not number
454
+ );
455
+ // Only re-renders when crossing the 768px threshold
456
+ }
457
+ ```
458
+
459
+ ### Lazy State Initialization
460
+
461
+ ```typescript
462
+ // BAD: Expensive computation runs on every render (result is ignored after first)
463
+ const [data, setData] = useState(parseExpensiveJSON(localStorage.getItem('data')));
464
+
465
+ // GOOD: Function is only called once on mount
466
+ const [data, setData] = useState(() => parseExpensiveJSON(localStorage.getItem('data')));
467
+ ```
468
+
469
+ ### Extract Memoized Components
470
+
471
+ ```tsx
472
+ // When a parent re-renders but a child's props don't change:
473
+ const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
474
+ return items.map(item => <ExpensiveItem key={item.id} item={item} />);
475
+ });
476
+
477
+ // Use with useCallback for event handlers
478
+ const handleClick = useCallback((id: string) => {
479
+ setSelected(id);
480
+ }, []); // Stable reference
481
+
482
+ <ExpensiveList items={items} onClick={handleClick} />
483
+ ```
484
+
485
+ ### useTransition for Non-Urgent Updates
486
+
487
+ ```typescript
488
+ function SearchWithSuggestions() {
489
+ const [query, setQuery] = useState('');
490
+ const [results, setResults] = useState([]);
491
+ const [isPending, startTransition] = useTransition();
492
+
493
+ function handleChange(value: string) {
494
+ setQuery(value); // Urgent: update input immediately
495
+ startTransition(() => {
496
+ setResults(filterResults(value)); // Non-urgent: can be interrupted
497
+ });
498
+ }
499
+
500
+ return (
501
+ <>
502
+ <input value={query} onChange={e => handleChange(e.target.value)} />
503
+ <div style={{ opacity: isPending ? 0.7 : 1 }}>
504
+ <ResultsList results={results} />
505
+ </div>
506
+ </>
507
+ );
508
+ }
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Priority 6 - MEDIUM: Rendering Performance
514
+
515
+ ### content-visibility: auto for Off-Screen DOM
516
+
517
+ 10x faster initial render for long pages. Browser skips layout/paint for off-screen elements.
518
+
519
+ ```css
520
+ .card {
521
+ content-visibility: auto;
522
+ contain-intrinsic-size: auto 300px; /* Estimated height to prevent scroll jump */
523
+ }
524
+
525
+ /* For a list of items */
526
+ .list-item {
527
+ content-visibility: auto;
528
+ contain-intrinsic-size: auto 80px;
529
+ }
530
+ ```
531
+
532
+ ### Hoist Static JSX Outside Components
533
+
534
+ Static elements don't need to be recreated on every render.
535
+
536
+ ```tsx
537
+ // BAD: emptyState is recreated on every render of List
538
+ function List({ items }) {
539
+ const emptyState = (
540
+ <div className="empty">
541
+ <p>No items found</p>
542
+ </div>
543
+ );
544
+
545
+ if (items.length === 0) return emptyState;
546
+ return <ul>{items.map(renderItem)}</ul>;
547
+ }
548
+
549
+ // GOOD: Created once, reused forever
550
+ const emptyState = (
551
+ <div className="empty">
552
+ <p>No items found</p>
553
+ </div>
554
+ );
555
+
556
+ function List({ items }) {
557
+ if (items.length === 0) return emptyState;
558
+ return <ul>{items.map(renderItem)}</ul>;
559
+ }
560
+ ```
561
+
562
+ ### Prevent Hydration Mismatch with Inline Script
563
+
564
+ For theme, auth state, or locale that must be available before React hydrates:
565
+
566
+ ```tsx
567
+ // layout.tsx
568
+ <head>
569
+ <script
570
+ dangerouslySetInnerHTML={{
571
+ __html: `
572
+ (function() {
573
+ var theme = localStorage.getItem('theme') || 'system';
574
+ if (theme === 'system') {
575
+ theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
576
+ }
577
+ document.documentElement.setAttribute('data-theme', theme);
578
+ document.documentElement.style.colorScheme = theme;
579
+ })();
580
+ `,
581
+ }}
582
+ />
583
+ </head>
584
+ ```
585
+
586
+ This runs synchronously before React hydrates, preventing the flash of wrong theme.
587
+
588
+ ### Optimize SVG Precision
589
+
590
+ ```bash
591
+ # Default SVG from design tools: way too many decimal places
592
+ # <path d="M12.456789 34.567891 L56.789012 78.901234" />
593
+
594
+ # After svgo --precision=1:
595
+ # <path d="M12.5 34.6 L56.8 78.9" />
596
+
597
+ npx svgo --precision=1 --multipass icons/*.svg
598
+ ```
599
+
600
+ Reduces SVG file size by 20-40% with no visible difference.
601
+
602
+ ### Use Activity Component for Show/Hide
603
+
604
+ React 19+ `<Activity>` preserves state and DOM when hiding, instead of unmounting.
605
+
606
+ ```tsx
607
+ import { Activity } from 'react';
608
+
609
+ function TabPanel({ activeTab }) {
610
+ return (
611
+ <>
612
+ <Activity mode={activeTab === 'editor' ? 'visible' : 'hidden'}>
613
+ <CodeEditor /> {/* State preserved when hidden */}
614
+ </Activity>
615
+ <Activity mode={activeTab === 'preview' ? 'visible' : 'hidden'}>
616
+ <Preview /> {/* State preserved when hidden */}
617
+ </Activity>
618
+ </>
619
+ );
620
+ }
621
+ ```
622
+
623
+ ### Explicit Conditional Rendering
624
+
625
+ ```tsx
626
+ // BAD: && can render "0" or "NaN" as text
627
+ {count && <Badge count={count} />}
628
+ // If count is 0, renders "0" as text in the DOM
629
+
630
+ // GOOD: Explicit boolean check
631
+ {count > 0 ? <Badge count={count} /> : null}
632
+
633
+ // GOOD: Double negation for truthy check
634
+ {!!items.length ? <List items={items} /> : <EmptyState />}
635
+ ```
636
+
637
+ ---
638
+
639
+ ## Priority 7 - LOW-MEDIUM: JavaScript Performance
640
+
641
+ ### Build Index Maps for O(1) Lookups
642
+
643
+ ```typescript
644
+ // BAD: O(n) lookup on every render or event
645
+ function findUser(users: User[], id: string) {
646
+ return users.find(u => u.id === id); // Scans entire array
647
+ }
648
+
649
+ // GOOD: O(1) lookup after one-time O(n) index build
650
+ const userIndex = useMemo(() => {
651
+ const map = new Map<string, User>();
652
+ for (const user of users) {
653
+ map.set(user.id, user);
654
+ }
655
+ return map;
656
+ }, [users]);
657
+
658
+ function findUser(id: string) {
659
+ return userIndex.get(id); // Instant lookup
660
+ }
661
+ ```
662
+
663
+ ### Set/Map for Membership Checks
664
+
665
+ ```typescript
666
+ // BAD: O(n) per check
667
+ const selectedIds = ['a', 'b', 'c', ...hundredsMore];
668
+ items.filter(item => selectedIds.includes(item.id)); // O(n*m)
669
+
670
+ // GOOD: O(1) per check
671
+ const selectedSet = new Set(selectedIds);
672
+ items.filter(item => selectedSet.has(item.id)); // O(n)
673
+ ```
674
+
675
+ ### Combine Array Iterations
676
+
677
+ ```typescript
678
+ // BAD: Three passes over the array
679
+ const active = users.filter(u => u.active);
680
+ const names = active.map(u => u.name);
681
+ const sorted = names.sort();
682
+
683
+ // GOOD: Single pass + sort
684
+ const names: string[] = [];
685
+ for (const u of users) {
686
+ if (u.active) names.push(u.name);
687
+ }
688
+ names.sort();
689
+ ```
690
+
691
+ This matters when arrays have 1000+ items or the operation runs frequently (on scroll, on keystroke).
692
+
693
+ ### Cache Storage API Calls
694
+
695
+ ```typescript
696
+ // BAD: Synchronous localStorage call on every render
697
+ function useTheme() {
698
+ const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');
699
+ // localStorage.getItem is synchronous and blocks the main thread
700
+ }
701
+
702
+ // GOOD: Read once, cache in memory
703
+ let cachedTheme: string | null = null;
704
+ function getTheme() {
705
+ if (cachedTheme === null) {
706
+ cachedTheme = localStorage.getItem('theme') || 'light';
707
+ }
708
+ return cachedTheme;
709
+ }
710
+
711
+ function setTheme(theme: string) {
712
+ cachedTheme = theme;
713
+ localStorage.setItem('theme', theme); // Write-through
714
+ }
715
+ ```
716
+
717
+ ### toSorted() Instead of sort()
718
+
719
+ ```typescript
720
+ // BAD: Mutates the original array (breaks React state rules)
721
+ const sorted = items.sort((a, b) => a.name.localeCompare(b.name));
722
+ // items is now mutated! React won't detect the change
723
+
724
+ // GOOD: Returns new sorted array (immutable, React-safe)
725
+ const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name));
726
+ // items is unchanged, sorted is a new array
727
+
728
+ // Also: toReversed(), toSpliced(), with()
729
+ const reversed = items.toReversed();
730
+ const withRemoval = items.toSpliced(2, 1); // Remove item at index 2
731
+ const withReplacement = items.with(3, newItem); // Replace item at index 3
732
+ ```
733
+
734
+ ---
735
+
736
+ ## Quick Reference: When to Optimize What
737
+
738
+ | Symptom | Check First |
739
+ |---------|------------|
740
+ | Slow page load (3s+) | Priority 1: waterfalls in data fetching |
741
+ | Large JS bundle (500KB+) | Priority 2: barrel imports, dynamic imports |
742
+ | Slow server response | Priority 3: caching, parallel fetching |
743
+ | Stale data, extra requests | Priority 4: SWR, deduplication |
744
+ | Janky typing/scrolling | Priority 5: re-render isolation, useTransition |
745
+ | Slow initial paint | Priority 6: content-visibility, hydration |
746
+ | Slow interactions on large lists | Priority 7: Maps, Sets, combined iterations |