RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/AGENTS.md/CherryHQ/cherry-studio

AGENTS.md

.agents/skills/vercel-react-best-practices/AGENTS.md
AGENTS.md

Quality

64/100

Scores the file, not the repository.

Length

11,044 words

74 headings · 146 code blocks

Repository

49k

— · pushed 0 days ago

Last changed

3 days ago

First indexed 3 days ago.
CherryHQ/cherry-studio/.agents/skills/vercel-react-best-practices/AGENTS.mdRawGitHub
1# React Best Practices
2 
3**Version 1.0.0**
4Vercel Engineering
5January 2026
6 
7> **Note:**
8> This document is mainly for agents and LLMs to follow when maintaining,
9> generating, or refactoring React and Next.js 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## Abstract
16 
17Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.
18 
19---
20 
21## Table of Contents
22 
231. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**
24 - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)
25 - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)
26 - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)
27 - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)
28 - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)
292. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**
30 - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)
31 - 2.2 [Conditional Module Loading](#22-conditional-module-loading)
32 - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)
33 - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)
34 - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)
353. [Server-Side Performance](#3-server-side-performance) — **HIGH**
36 - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)
37 - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)
38 - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)
39 - 3.4 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)
40 - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)
41 - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)
42 - 3.7 [Per-Request Deduplication with React.cache()](#37-per-request-deduplication-with-reactcache)
43 - 3.8 [Use after() for Non-Blocking Operations](#38-use-after-for-non-blocking-operations)
444. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**
45 - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)
46 - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)
47 - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)
48 - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)
495. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**
50 - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)
51 - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)
52 - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)
53 - 5.4 [Don't Define Components Inside Components](#54-dont-define-components-inside-components)
54 - 5.5 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#55-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
55 - 5.6 [Extract to Memoized Components](#56-extract-to-memoized-components)
56 - 5.7 [Narrow Effect Dependencies](#57-narrow-effect-dependencies)
57 - 5.8 [Put Interaction Logic in Event Handlers](#58-put-interaction-logic-in-event-handlers)
58 - 5.9 [Subscribe to Derived State](#59-subscribe-to-derived-state)
59 - 5.10 [Use Functional setState Updates](#510-use-functional-setstate-updates)
60 - 5.11 [Use Lazy State Initialization](#511-use-lazy-state-initialization)
61 - 5.12 [Use Transitions for Non-Urgent Updates](#512-use-transitions-for-non-urgent-updates)
62 - 5.13 [Use useRef for Transient Values](#513-use-useref-for-transient-values)
636. [Rendering Performance](#6-rendering-performance) — **MEDIUM**
64 - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)
65 - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)
66 - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)
67 - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)
68 - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)
69 - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)
70 - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)
71 - 6.8 [Use defer or async on Script Tags](#68-use-defer-or-async-on-script-tags)
72 - 6.9 [Use Explicit Conditional Rendering](#69-use-explicit-conditional-rendering)
73 - 6.10 [Use React DOM Resource Hints](#610-use-react-dom-resource-hints)
74 - 6.11 [Use useTransition Over Manual Loading States](#611-use-usetransition-over-manual-loading-states)
757. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**
76 - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)
77 - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)
78 - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)
79 - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)
80 - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)
81 - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)
82 - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)
83 - 7.8 [Early Return from Functions](#78-early-return-from-functions)
84 - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)
85 - 7.10 [Use flatMap to Map and Filter in One Pass](#710-use-flatmap-to-map-and-filter-in-one-pass)
86 - 7.11 [Use Loop for Min/Max Instead of Sort](#711-use-loop-for-minmax-instead-of-sort)
87 - 7.12 [Use Set/Map for O(1) Lookups](#712-use-setmap-for-o1-lookups)
88 - 7.13 [Use toSorted() Instead of sort() for Immutability](#713-use-tosorted-instead-of-sort-for-immutability)
898. [Advanced Patterns](#8-advanced-patterns) — **LOW**
90 - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)
91 - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)
92 - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)
93 
94---
95 
96## 1. Eliminating Waterfalls
97 
98**Impact: CRITICAL**
99 
100Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.
101 
102### 1.1 Defer Await Until Needed
103 
104**Impact: HIGH (avoids blocking unused code paths)**
105 
106Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.
107 
108**Incorrect: blocks both branches**
109 
110```typescript
111async function handleRequest(userId: string, skipProcessing: boolean) {
112 const userData = await fetchUserData(userId)
113
114 if (skipProcessing) {
115 // Returns immediately but still waited for userData
116 return { skipped: true }
117 }
118
119 // Only this branch uses userData
120 return processUserData(userData)
121}
122```
123 
124**Correct: only blocks when needed**
125 
126```typescript
127async function handleRequest(userId: string, skipProcessing: boolean) {
128 if (skipProcessing) {
129 // Returns immediately without waiting
130 return { skipped: true }
131 }
132
133 // Fetch only when needed
134 const userData = await fetchUserData(userId)
135 return processUserData(userData)
136}
137```
138 
139**Another example: early return optimization**
140 
141```typescript
142// Incorrect: always fetches permissions
143async function updateResource(resourceId: string, userId: string) {
144 const permissions = await fetchPermissions(userId)
145 const resource = await getResource(resourceId)
146
147 if (!resource) {
148 return { error: 'Not found' }
149 }
150
151 if (!permissions.canEdit) {
152 return { error: 'Forbidden' }
153 }
154
155 return await updateResourceData(resource, permissions)
156}
157 
158// Correct: fetches only when needed
159async function updateResource(resourceId: string, userId: string) {
160 const resource = await getResource(resourceId)
161
162 if (!resource) {
163 return { error: 'Not found' }
164 }
165
166 const permissions = await fetchPermissions(userId)
167
168 if (!permissions.canEdit) {
169 return { error: 'Forbidden' }
170 }
171
172 return await updateResourceData(resource, permissions)
173}
174```
175 
176This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.
177 
178### 1.2 Dependency-Based Parallelization
179 
180**Impact: CRITICAL (2-10× improvement)**
181 
182For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
183 
184**Incorrect: profile waits for config unnecessarily**
185 
186```typescript
187const [user, config] = await Promise.all([
188 fetchUser(),
189 fetchConfig()
190])
191const profile = await fetchProfile(user.id)
192```
193 
194**Correct: config and profile run in parallel**
195 
196```typescript
197import { all } from 'better-all'
198 
199const { user, config, profile } = await all({
200 async user() { return fetchUser() },
201 async config() { return fetchConfig() },
202 async profile() {
203 return fetchProfile((await this.$.user).id)
204 }
205})
206```
207 
208**Alternative without extra dependencies:**
209 
210```typescript
211const userPromise = fetchUser()
212const profilePromise = userPromise.then(user => fetchProfile(user.id))
213 
214const [user, config, profile] = await Promise.all([
215 userPromise,
216 fetchConfig(),
217 profilePromise
218])
219```
220 
221We can also create all the promises first, and do `Promise.all()` at the end.
222 
223Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
224 
225### 1.3 Prevent Waterfall Chains in API Routes
226 
227**Impact: CRITICAL (2-10× improvement)**
228 
229In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.
230 
231**Incorrect: config waits for auth, data waits for both**
232 
233```typescript
234export async function GET(request: Request) {
235 const session = await auth()
236 const config = await fetchConfig()
237 const data = await fetchData(session.user.id)
238 return Response.json({ data, config })
239}
240```
241 
242**Correct: auth and config start immediately**
243 
244```typescript
245export async function GET(request: Request) {
246 const sessionPromise = auth()
247 const configPromise = fetchConfig()
248 const session = await sessionPromise
249 const [config, data] = await Promise.all([
250 configPromise,
251 fetchData(session.user.id)
252 ])
253 return Response.json({ data, config })
254}
255```
256 
257For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
258 
259### 1.4 Promise.all() for Independent Operations
260 
261**Impact: CRITICAL (2-10× improvement)**
262 
263When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
264 
265**Incorrect: sequential execution, 3 round trips**
266 
267```typescript
268const user = await fetchUser()
269const posts = await fetchPosts()
270const comments = await fetchComments()
271```
272 
273**Correct: parallel execution, 1 round trip**
274 
275```typescript
276const [user, posts, comments] = await Promise.all([
277 fetchUser(),
278 fetchPosts(),
279 fetchComments()
280])
281```
282 
283### 1.5 Strategic Suspense Boundaries
284 
285**Impact: HIGH (faster initial paint)**
286 
287Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
288 
289**Incorrect: wrapper blocked by data fetching**
290 
291```tsx
292async function Page() {
293 const data = await fetchData() // Blocks entire page
294
295 return (
296 <div>
297 <div>Sidebar</div>
298 <div>Header</div>
299 <div>
300 <DataDisplay data={data} />
301 </div>
302 <div>Footer</div>
303 </div>
304 )
305}
306```
307 
308The entire layout waits for data even though only the middle section needs it.
309 
310**Correct: wrapper shows immediately, data streams in**
311 
312```tsx
313function Page() {
314 return (
315 <div>
316 <div>Sidebar</div>
317 <div>Header</div>
318 <div>
319 <Suspense fallback={<Skeleton />}>
320 <DataDisplay />
321 </Suspense>
322 </div>
323 <div>Footer</div>
324 </div>
325 )
326}
327 
328async function DataDisplay() {
329 const data = await fetchData() // Only blocks this component
330 return <div>{data.content}</div>
331}
332```
333 
334Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
335 
336**Alternative: share promise across components**
337 
338```tsx
339function Page() {
340 // Start fetch immediately, but don't await
341 const dataPromise = fetchData()
342
343 return (
344 <div>
345 <div>Sidebar</div>
346 <div>Header</div>
347 <Suspense fallback={<Skeleton />}>
348 <DataDisplay dataPromise={dataPromise} />
349 <DataSummary dataPromise={dataPromise} />
350 </Suspense>
351 <div>Footer</div>
352 </div>
353 )
354}
355 
356function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
357 const data = use(dataPromise) // Unwraps the promise
358 return <div>{data.content}</div>
359}
360 
361function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
362 const data = use(dataPromise) // Reuses the same promise
363 return <div>{data.summary}</div>
364}
365```
366 
367Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
368 
369**When NOT to use this pattern:**
370 
371- Critical data needed for layout decisions (affects positioning)
372 
373- SEO-critical content above the fold
374 
375- Small, fast queries where suspense overhead isn't worth it
376 
377- When you want to avoid layout shift (loading → content jump)
378 
379**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
380 
381---
382 
383## 2. Bundle Size Optimization
384 
385**Impact: CRITICAL**
386 
387Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.
388 
389### 2.1 Avoid Barrel File Imports
390 
391**Impact: CRITICAL (200-800ms import cost, slow builds)**
392 
393Import 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'`).
394 
395Popular 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.
396 
397**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.
398 
399**Incorrect: imports entire library**
400 
401```tsx
402import { Check, X, Menu } from 'lucide-react'
403// Loads 1,583 modules, takes ~2.8s extra in dev
404// Runtime cost: 200-800ms on every cold start
405 
406import { Button, TextField } from '@mui/material'
407// Loads 2,225 modules, takes ~4.2s extra in dev
408```
409 
410**Correct: imports only what you need**
411 
412```tsx
413import Check from 'lucide-react/dist/esm/icons/check'
414import X from 'lucide-react/dist/esm/icons/x'
415import Menu from 'lucide-react/dist/esm/icons/menu'
416// Loads only 3 modules (~2KB vs ~1MB)
417 
418import Button from '@mui/material/Button'
419import TextField from '@mui/material/TextField'
420// Loads only what you use
421```
422 
423**Alternative: Next.js 13.5+**
424 
425```js
426// next.config.js - use optimizePackageImports
427module.exports = {
428 experimental: {
429 optimizePackageImports: ['lucide-react', '@mui/material']
430 }
431}
432 
433// Then you can keep the ergonomic barrel imports:
434import { Check, X, Menu } from 'lucide-react'
435// Automatically transformed to direct imports at build time
436```
437 
438Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
439 
440Libraries 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`.
441 
442Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
443 
444### 2.2 Conditional Module Loading
445 
446**Impact: HIGH (loads large data only when needed)**
447 
448Load large data or modules only when a feature is activated.
449 
450**Example: lazy-load animation frames**
451 
452```tsx
453function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {
454 const [frames, setFrames] = useState<Frame[] | null>(null)
455 
456 useEffect(() => {
457 if (enabled && !frames && typeof window !== 'undefined') {
458 import('./animation-frames.js')
459 .then(mod => setFrames(mod.frames))
460 .catch(() => setEnabled(false))
461 }
462 }, [enabled, frames, setEnabled])
463 
464 if (!frames) return <Skeleton />
465 return <Canvas frames={frames} />
466}
467```
468 
469The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
470 
471### 2.3 Defer Non-Critical Third-Party Libraries
472 
473**Impact: MEDIUM (loads after hydration)**
474 
475Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
476 
477**Incorrect: blocks initial bundle**
478 
479```tsx
480import { Analytics } from '@vercel/analytics/react'
481 
482export default function RootLayout({ children }) {
483 return (
484 <html>
485 <body>
486 {children}
487 <Analytics />
488 </body>
489 </html>
490 )
491}
492```
493 
494**Correct: loads after hydration**
495 
496```tsx
497import dynamic from 'next/dynamic'
498 
499const Analytics = dynamic(
500 () => import('@vercel/analytics/react').then(m => m.Analytics),
501 { ssr: false }
502)
503 
504export default function RootLayout({ children }) {
505 return (
506 <html>
507 <body>
508 {children}
509 <Analytics />
510 </body>
511 </html>
512 )
513}
514```
515 
516### 2.4 Dynamic Imports for Heavy Components
517 
518**Impact: CRITICAL (directly affects TTI and LCP)**
519 
520Use `next/dynamic` to lazy-load large components not needed on initial render.
521 
522**Incorrect: Monaco bundles with main chunk ~300KB**
523 
524```tsx
525import { MonacoEditor } from './monaco-editor'
526 
527function CodePanel({ code }: { code: string }) {
528 return <MonacoEditor value={code} />
529}
530```
531 
532**Correct: Monaco loads on demand**
533 
534```tsx
535import dynamic from 'next/dynamic'
536 
537const MonacoEditor = dynamic(
538 () => import('./monaco-editor').then(m => m.MonacoEditor),
539 { ssr: false }
540)
541 
542function CodePanel({ code }: { code: string }) {
543 return <MonacoEditor value={code} />
544}
545```
546 
547### 2.5 Preload Based on User Intent
548 
549**Impact: MEDIUM (reduces perceived latency)**
550 
551Preload heavy bundles before they're needed to reduce perceived latency.
552 
553**Example: preload on hover/focus**
554 
555```tsx
556function EditorButton({ onClick }: { onClick: () => void }) {
557 const preload = () => {
558 if (typeof window !== 'undefined') {
559 void import('./monaco-editor')
560 }
561 }
562 
563 return (
564 <button
565 onMouseEnter={preload}
566 onFocus={preload}
567 onClick={onClick}
568 >
569 Open Editor
570 </button>
571 )
572}
573```
574 
575**Example: preload when feature flag is enabled**
576 
577```tsx
578function FlagsProvider({ children, flags }: Props) {
579 useEffect(() => {
580 if (flags.editorEnabled && typeof window !== 'undefined') {
581 void import('./monaco-editor').then(mod => mod.init())
582 }
583 }, [flags.editorEnabled])
584 
585 return <FlagsContext.Provider value={flags}>
586 {children}
587 </FlagsContext.Provider>
588}
589```
590 
591The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
592 
593---
594 
595## 3. Server-Side Performance
596 
597**Impact: HIGH**
598 
599Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.
600 
601### 3.1 Authenticate Server Actions Like API Routes
602 
603**Impact: CRITICAL (prevents unauthorized access to server mutations)**
604 
605Server Actions (functions with `"use server"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.
606 
607Next.js documentation explicitly states: "Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation."
608 
609**Incorrect: no authentication check**
610 
611```typescript
612'use server'
613 
614export async function deleteUser(userId: string) {
615 // Anyone can call this! No auth check
616 await db.user.delete({ where: { id: userId } })
617 return { success: true }
618}
619```
620 
621**Correct: authentication inside the action**
622 
623```typescript
624'use server'
625 
626import { verifySession } from '@/lib/auth'
627import { unauthorized } from '@/lib/errors'
628 
629export async function deleteUser(userId: string) {
630 // Always check auth inside the action
631 const session = await verifySession()
632
633 if (!session) {
634 throw unauthorized('Must be logged in')
635 }
636
637 // Check authorization too
638 if (session.user.role !== 'admin' && session.user.id !== userId) {
639 throw unauthorized('Cannot delete other users')
640 }
641
642 await db.user.delete({ where: { id: userId } })
643 return { success: true }
644}
645```
646 
647**With input validation:**
648 
649```typescript
650'use server'
651 
652import { verifySession } from '@/lib/auth'
653import { z } from 'zod'
654 
655const updateProfileSchema = z.object({
656 userId: z.string().uuid(),
657 name: z.string().min(1).max(100),
658 email: z.string().email()
659})
660 
661export async function updateProfile(data: unknown) {
662 // Validate input first
663 const validated = updateProfileSchema.parse(data)
664
665 // Then authenticate
666 const session = await verifySession()
667 if (!session) {
668 throw new Error('Unauthorized')
669 }
670
671 // Then authorize
672 if (session.user.id !== validated.userId) {
673 throw new Error('Can only update own profile')
674 }
675
676 // Finally perform the mutation
677 await db.user.update({
678 where: { id: validated.userId },
679 data: {
680 name: validated.name,
681 email: validated.email
682 }
683 })
684
685 return { success: true }
686}
687```
688 
689Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)
690 
691### 3.2 Avoid Duplicate Serialization in RSC Props
692 
693**Impact: LOW (reduces network payload by avoiding duplicate serialization)**
694 
695RSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.
696 
697**Incorrect: duplicates array**
698 
699```tsx
700// RSC: sends 6 strings (2 arrays × 3 items)
701<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />
702```
703 
704**Correct: sends 3 strings**
705 
706```tsx
707// RSC: send once
708<ClientList usernames={usernames} />
709 
710// Client: transform there
711'use client'
712const sorted = useMemo(() => [...usernames].sort(), [usernames])
713```
714 
715**Nested deduplication behavior:**
716 
717```tsx
718// string[] - duplicates everything
719usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings
720 
721// object[] - duplicates array structure only
722users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)
723```
724 
725Deduplication works recursively. Impact varies by data type:
726 
727- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated
728 
729- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference
730 
731**Operations breaking deduplication: create new references**
732 
733- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`
734 
735- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`
736 
737**More examples:**
738 
739```tsx
740// ❌ Bad
741<C users={users} active={users.filter(u => u.active)} />
742<C product={product} productName={product.name} />
743 
744// ✅ Good
745<C users={users} />
746<C product={product} />
747// Do filtering/destructuring in client
748```
749 
750**Exception:** Pass derived data when transformation is expensive or client doesn't need original.
751 
752### 3.3 Cross-Request LRU Caching
753 
754**Impact: HIGH (caches across requests)**
755 
756`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.
757 
758**Implementation:**
759 
760```typescript
761import { LRUCache } from 'lru-cache'
762 
763const cache = new LRUCache<string, any>({
764 max: 1000,
765 ttl: 5 * 60 * 1000 // 5 minutes
766})
767 
768export async function getUser(id: string) {
769 const cached = cache.get(id)
770 if (cached) return cached
771 
772 const user = await db.user.findUnique({ where: { id } })
773 cache.set(id, user)
774 return user
775}
776 
777// Request 1: DB query, result cached
778// Request 2: cache hit, no DB query
779```
780 
781Use when sequential user actions hit multiple endpoints needing the same data within seconds.
782 
783**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.
784 
785**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.
786 
787Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
788 
789### 3.4 Hoist Static I/O to Module Level
790 
791**Impact: HIGH (avoids repeated file/network I/O per request)**
792 
793When loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.
794 
795**Incorrect: reads font file on every request**
796 
797**Correct: loads once at module initialization**
798 
799**Alternative: synchronous file reads with Node.js fs**
800 
801**General Node.js example: loading config or templates**
802 
803**When to use this pattern:**
804 
805- Loading fonts for OG image generation
806 
807- Loading static logos, icons, or watermarks
808 
809- Reading configuration files that don't change at runtime
810 
811- Loading email templates or other static templates
812 
813- Any static asset that's the same across all requests
814 
815**When NOT to use this pattern:**
816 
817- Assets that vary per request or user
818 
819- Files that may change during runtime (use caching with TTL instead)
820 
821- Large files that would consume too much memory if kept loaded
822 
823- Sensitive data that shouldn't persist in memory
824 
825**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.
826 
827**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.
828 
829### 3.5 Minimize Serialization at RSC Boundaries
830 
831**Impact: HIGH (reduces data transfer size)**
832 
833The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
834 
835**Incorrect: serializes all 50 fields**
836 
837```tsx
838async function Page() {
839 const user = await fetchUser() // 50 fields
840 return <Profile user={user} />
841}
842 
843'use client'
844function Profile({ user }: { user: User }) {
845 return <div>{user.name}</div> // uses 1 field
846}
847```
848 
849**Correct: serializes only 1 field**
850 
851```tsx
852async function Page() {
853 const user = await fetchUser()
854 return <Profile name={user.name} />
855}
856 
857'use client'
858function Profile({ name }: { name: string }) {
859 return <div>{name}</div>
860}
861```
862 
863### 3.6 Parallel Data Fetching with Component Composition
864 
865**Impact: CRITICAL (eliminates server-side waterfalls)**
866 
867React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.
868 
869**Incorrect: Sidebar waits for Page's fetch to complete**
870 
871```tsx
872export default async function Page() {
873 const header = await fetchHeader()
874 return (
875 <div>
876 <div>{header}</div>
877 <Sidebar />
878 </div>
879 )
880}
881 
882async function Sidebar() {
883 const items = await fetchSidebarItems()
884 return <nav>{items.map(renderItem)}</nav>
885}
886```
887 
888**Correct: both fetch simultaneously**
889 
890```tsx
891async function Header() {
892 const data = await fetchHeader()
893 return <div>{data}</div>
894}
895 
896async function Sidebar() {
897 const items = await fetchSidebarItems()
898 return <nav>{items.map(renderItem)}</nav>
899}
900 
901export default function Page() {
902 return (
903 <div>
904 <Header />
905 <Sidebar />
906 </div>
907 )
908}
909```
910 
911**Alternative with children prop:**
912 
913```tsx
914async function Header() {
915 const data = await fetchHeader()
916 return <div>{data}</div>
917}
918 
919async function Sidebar() {
920 const items = await fetchSidebarItems()
921 return <nav>{items.map(renderItem)}</nav>
922}
923 
924function Layout({ children }: { children: ReactNode }) {
925 return (
926 <div>
927 <Header />
928 {children}
929 </div>
930 )
931}
932 
933export default function Page() {
934 return (
935 <Layout>
936 <Sidebar />
937 </Layout>
938 )
939}
940```
941 
942### 3.7 Per-Request Deduplication with React.cache()
943 
944**Impact: MEDIUM (deduplicates within request)**
945 
946Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.
947 
948**Usage:**
949 
950```typescript
951import { cache } from 'react'
952 
953export const getCurrentUser = cache(async () => {
954 const session = await auth()
955 if (!session?.user?.id) return null
956 return await db.user.findUnique({
957 where: { id: session.user.id }
958 })
959})
960```
961 
962Within a single request, multiple calls to `getCurrentUser()` execute the query only once.
963 
964**Avoid inline objects as arguments:**
965 
966`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.
967 
968**Incorrect: always cache miss**
969 
970```typescript
971const getUser = cache(async (params: { uid: number }) => {
972 return await db.user.findUnique({ where: { id: params.uid } })
973})
974 
975// Each call creates new object, never hits cache
976getUser({ uid: 1 })
977getUser({ uid: 1 }) // Cache miss, runs query again
978```
979 
980**Correct: cache hit**
981 
982```typescript
983const params = { uid: 1 }
984getUser(params) // Query runs
985getUser(params) // Cache hit (same reference)
986```
987 
988If you must pass objects, pass the same reference:
989 
990**Next.js-Specific Note:**
991 
992In Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:
993 
994- Database queries (Prisma, Drizzle, etc.)
995 
996- Heavy computations
997 
998- Authentication checks
999 
1000- File system operations
1001 
1002- Any non-fetch async work
1003 
1004Use `React.cache()` to deduplicate these operations across your component tree.
1005 
1006Reference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)
1007 
1008### 3.8 Use after() for Non-Blocking Operations
1009 
1010**Impact: MEDIUM (faster response times)**
1011 
1012Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.
1013 
1014**Incorrect: blocks response**
1015 
1016```tsx
1017import { logUserAction } from '@/app/utils'
1018 
1019export async function POST(request: Request) {
1020 // Perform mutation
1021 await updateDatabase(request)
1022
1023 // Logging blocks the response
1024 const userAgent = request.headers.get('user-agent') || 'unknown'
1025 await logUserAction({ userAgent })
1026
1027 return new Response(JSON.stringify({ status: 'success' }), {
1028 status: 200,
1029 headers: { 'Content-Type': 'application/json' }
1030 })
1031}
1032```
1033 
1034**Correct: non-blocking**
1035 
1036```tsx
1037import { after } from 'next/server'
1038import { headers, cookies } from 'next/headers'
1039import { logUserAction } from '@/app/utils'
1040 
1041export async function POST(request: Request) {
1042 // Perform mutation
1043 await updateDatabase(request)
1044
1045 // Log after response is sent
1046 after(async () => {
1047 const userAgent = (await headers()).get('user-agent') || 'unknown'
1048 const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
1049
1050 logUserAction({ sessionCookie, userAgent })
1051 })
1052
1053 return new Response(JSON.stringify({ status: 'success' }), {
1054 status: 200,
1055 headers: { 'Content-Type': 'application/json' }
1056 })
1057}
1058```
1059 
1060The response is sent immediately while logging happens in the background.
1061 
1062**Common use cases:**
1063 
1064- Analytics tracking
1065 
1066- Audit logging
1067 
1068- Sending notifications
1069 
1070- Cache invalidation
1071 
1072- Cleanup tasks
1073 
1074**Important notes:**
1075 
1076- `after()` runs even if the response fails or redirects
1077 
1078- Works in Server Actions, Route Handlers, and Server Components
1079 
1080Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)
1081 
1082---
1083 
1084## 4. Client-Side Data Fetching
1085 
1086**Impact: MEDIUM-HIGH**
1087 
1088Automatic deduplication and efficient data fetching patterns reduce redundant network requests.
1089 
1090### 4.1 Deduplicate Global Event Listeners
1091 
1092**Impact: LOW (single listener for N components)**
1093 
1094Use `useSWRSubscription()` to share global event listeners across component instances.
1095 
1096**Incorrect: N instances = N listeners**
1097 
1098```tsx
1099function useKeyboardShortcut(key: string, callback: () => void) {
1100 useEffect(() => {
1101 const handler = (e: KeyboardEvent) => {
1102 if (e.metaKey && e.key === key) {
1103 callback()
1104 }
1105 }
1106 window.addEventListener('keydown', handler)
1107 return () => window.removeEventListener('keydown', handler)
1108 }, [key, callback])
1109}
1110```
1111 
1112When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
1113 
1114**Correct: N instances = 1 listener**
1115 
1116```tsx
1117import useSWRSubscription from 'swr/subscription'
1118 
1119// Module-level Map to track callbacks per key
1120const keyCallbacks = new Map<string, Set<() => void>>()
1121 
1122function useKeyboardShortcut(key: string, callback: () => void) {
1123 // Register this callback in the Map
1124 useEffect(() => {
1125 if (!keyCallbacks.has(key)) {
1126 keyCallbacks.set(key, new Set())
1127 }
1128 keyCallbacks.get(key)!.add(callback)
1129 
1130 return () => {
1131 const set = keyCallbacks.get(key)
1132 if (set) {
1133 set.delete(callback)
1134 if (set.size === 0) {
1135 keyCallbacks.delete(key)
1136 }
1137 }
1138 }
1139 }, [key, callback])
1140 
1141 useSWRSubscription('global-keydown', () => {
1142 const handler = (e: KeyboardEvent) => {
1143 if (e.metaKey && keyCallbacks.has(e.key)) {
1144 keyCallbacks.get(e.key)!.forEach(cb => cb())
1145 }
1146 }
1147 window.addEventListener('keydown', handler)
1148 return () => window.removeEventListener('keydown', handler)
1149 })
1150}
1151 
1152function Profile() {
1153 // Multiple shortcuts will share the same listener
1154 useKeyboardShortcut('p', () => { /* ... */ })
1155 useKeyboardShortcut('k', () => { /* ... */ })
1156 // ...
1157}
1158```
1159 
1160### 4.2 Use Passive Event Listeners for Scrolling Performance
1161 
1162**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**
1163 
1164Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.
1165 
1166**Incorrect:**
1167 
1168```typescript
1169useEffect(() => {
1170 const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
1171 const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
1172
1173 document.addEventListener('touchstart', handleTouch)
1174 document.addEventListener('wheel', handleWheel)
1175
1176 return () => {
1177 document.removeEventListener('touchstart', handleTouch)
1178 document.removeEventListener('wheel', handleWheel)
1179 }
1180}, [])
1181```
1182 
1183**Correct:**
1184 
1185```typescript
1186useEffect(() => {
1187 const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
1188 const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
1189
1190 document.addEventListener('touchstart', handleTouch, { passive: true })
1191 document.addEventListener('wheel', handleWheel, { passive: true })
1192
1193 return () => {
1194 document.removeEventListener('touchstart', handleTouch)
1195 document.removeEventListener('wheel', handleWheel)
1196 }
1197}, [])
1198```
1199 
1200**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.
1201 
1202**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.
1203 
1204### 4.3 Use SWR for Automatic Deduplication
1205 
1206**Impact: MEDIUM-HIGH (automatic deduplication)**
1207 
1208SWR enables request deduplication, caching, and revalidation across component instances.
1209 
1210**Incorrect: no deduplication, each instance fetches**
1211 
1212```tsx
1213function UserList() {
1214 const [users, setUsers] = useState([])
1215 useEffect(() => {
1216 fetch('/api/users')
1217 .then(r => r.json())
1218 .then(setUsers)
1219 }, [])
1220}
1221```
1222 
1223**Correct: multiple instances share one request**
1224 
1225```tsx
1226import useSWR from 'swr'
1227 
1228function UserList() {
1229 const { data: users } = useSWR('/api/users', fetcher)
1230}
1231```
1232 
1233**For immutable data:**
1234 
1235```tsx
1236import { useImmutableSWR } from '@/lib/swr'
1237 
1238function StaticContent() {
1239 const { data } = useImmutableSWR('/api/config', fetcher)
1240}
1241```
1242 
1243**For mutations:**
1244 
1245```tsx
1246import { useSWRMutation } from 'swr/mutation'
1247 
1248function UpdateButton() {
1249 const { trigger } = useSWRMutation('/api/user', updateUser)
1250 return <button onClick={() => trigger()}>Update</button>
1251}
1252```
1253 
1254Reference: [https://swr.vercel.app](https://swr.vercel.app)
1255 
1256### 4.4 Version and Minimize localStorage Data
1257 
1258**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**
1259 
1260Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.
1261 
1262**Incorrect:**
1263 
1264```typescript
1265// No version, stores everything, no error handling
1266localStorage.setItem('userConfig', JSON.stringify(fullUserObject))
1267const data = localStorage.getItem('userConfig')
1268```
1269 
1270**Correct:**
1271 
1272```typescript
1273const VERSION = 'v2'
1274 
1275function saveConfig(config: { theme: string; language: string }) {
1276 try {
1277 localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
1278 } catch {
1279 // Throws in incognito/private browsing, quota exceeded, or disabled
1280 }
1281}
1282 
1283function loadConfig() {
1284 try {
1285 const data = localStorage.getItem(`userConfig:${VERSION}`)
1286 return data ? JSON.parse(data) : null
1287 } catch {
1288 return null
1289 }
1290}
1291 
1292// Migration from v1 to v2
1293function migrate() {
1294 try {
1295 const v1 = localStorage.getItem('userConfig:v1')
1296 if (v1) {
1297 const old = JSON.parse(v1)
1298 saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
1299 localStorage.removeItem('userConfig:v1')
1300 }
1301 } catch {}
1302}
1303```
1304 
1305**Store minimal fields from server responses:**
1306 
1307```typescript
1308// User object has 20+ fields, only store what UI needs
1309function cachePrefs(user: FullUser) {
1310 try {
1311 localStorage.setItem('prefs:v1', JSON.stringify({
1312 theme: user.preferences.theme,
1313 notifications: user.preferences.notifications
1314 }))
1315 } catch {}
1316}
1317```
1318 
1319**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
1320 
1321**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.
1322 
1323---
1324 
1325## 5. Re-render Optimization
1326 
1327**Impact: MEDIUM**
1328 
1329Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
1330 
1331### 5.1 Calculate Derived State During Rendering
1332 
1333**Impact: MEDIUM (avoids redundant renders and state drift)**
1334 
1335If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.
1336 
1337**Incorrect: redundant state and effect**
1338 
1339```tsx
1340function Form() {
1341 const [firstName, setFirstName] = useState('First')
1342 const [lastName, setLastName] = useState('Last')
1343 const [fullName, setFullName] = useState('')
1344 
1345 useEffect(() => {
1346 setFullName(firstName + ' ' + lastName)
1347 }, [firstName, lastName])
1348 
1349 return <p>{fullName}</p>
1350}
1351```
1352 
1353**Correct: derive during render**
1354 
1355```tsx
1356function Form() {
1357 const [firstName, setFirstName] = useState('First')
1358 const [lastName, setLastName] = useState('Last')
1359 const fullName = firstName + ' ' + lastName
1360 
1361 return <p>{fullName}</p>
1362}
1363```
1364 
1365Reference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)
1366 
1367### 5.2 Defer State Reads to Usage Point
1368 
1369**Impact: MEDIUM (avoids unnecessary subscriptions)**
1370 
1371Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
1372 
1373**Incorrect: subscribes to all searchParams changes**
1374 
1375```tsx
1376function ShareButton({ chatId }: { chatId: string }) {
1377 const searchParams = useSearchParams()
1378 
1379 const handleShare = () => {
1380 const ref = searchParams.get('ref')
1381 shareChat(chatId, { ref })
1382 }
1383 
1384 return <button onClick={handleShare}>Share</button>
1385}
1386```
1387 
1388**Correct: reads on demand, no subscription**
1389 
1390```tsx
1391function ShareButton({ chatId }: { chatId: string }) {
1392 const handleShare = () => {
1393 const params = new URLSearchParams(window.location.search)
1394 const ref = params.get('ref')
1395 shareChat(chatId, { ref })
1396 }
1397 
1398 return <button onClick={handleShare}>Share</button>
1399}
1400```
1401 
1402### 5.3 Do not wrap a simple expression with a primitive result type in useMemo
1403 
1404**Impact: LOW-MEDIUM (wasted computation on every render)**
1405 
1406When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.
1407 
1408Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.
1409 
1410**Incorrect:**
1411 
1412```tsx
1413function Header({ user, notifications }: Props) {
1414 const isLoading = useMemo(() => {
1415 return user.isLoading || notifications.isLoading
1416 }, [user.isLoading, notifications.isLoading])
1417 
1418 if (isLoading) return <Skeleton />
1419 // return some markup
1420}
1421```
1422 
1423**Correct:**
1424 
1425```tsx
1426function Header({ user, notifications }: Props) {
1427 const isLoading = user.isLoading || notifications.isLoading
1428 
1429 if (isLoading) return <Skeleton />
1430 // return some markup
1431}
1432```
1433 
1434### 5.4 Don't Define Components Inside Components
1435 
1436**Impact: HIGH (prevents remount on every render)**
1437 
1438Defining a component inside another component creates a new component type on every render. React sees a different component each time and fully remounts it, destroying all state and DOM.
1439 
1440A common reason developers do this is to access parent variables without passing props. Always pass props instead.
1441 
1442**Incorrect: remounts on every render**
1443 
1444```tsx
1445function UserProfile({ user, theme }) {
1446 // Defined inside to access `theme` - BAD
1447 const Avatar = () => (
1448 <img
1449 src={user.avatarUrl}
1450 className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
1451 />
1452 )
1453 
1454 // Defined inside to access `user` - BAD
1455 const Stats = () => (
1456 <div>
1457 <span>{user.followers} followers</span>
1458 <span>{user.posts} posts</span>
1459 </div>
1460 )
1461 
1462 return (
1463 <div>
1464 <Avatar />
1465 <Stats />
1466 </div>
1467 )
1468}
1469```
1470 
1471Every time `UserProfile` renders, `Avatar` and `Stats` are new component types. React unmounts the old instances and mounts new ones, losing any internal state, running effects again, and recreating DOM nodes.
1472 
1473**Correct: pass props instead**
1474 
1475```tsx
1476function Avatar({ src, theme }: { src: string; theme: string }) {
1477 return (
1478 <img
1479 src={src}
1480 className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
1481 />
1482 )
1483}
1484 
1485function Stats({ followers, posts }: { followers: number; posts: number }) {
1486 return (
1487 <div>
1488 <span>{followers} followers</span>
1489 <span>{posts} posts</span>
1490 </div>
1491 )
1492}
1493 
1494function UserProfile({ user, theme }) {
1495 return (
1496 <div>
1497 <Avatar src={user.avatarUrl} theme={theme} />
1498 <Stats followers={user.followers} posts={user.posts} />
1499 </div>
1500 )
1501}
1502```
1503 
1504**Symptoms of this bug:**
1505 
1506- Input fields lose focus on every keystroke
1507 
1508- Animations restart unexpectedly
1509 
1510- `useEffect` cleanup/setup runs on every parent render
1511 
1512- Scroll position resets inside the component
1513 
1514### 5.5 Extract Default Non-primitive Parameter Value from Memoized Component to Constant
1515 
1516**Impact: MEDIUM (restores memoization by using a constant for default value)**
1517 
1518When memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.
1519 
1520To address this issue, extract the default value into a constant.
1521 
1522**Incorrect: `onClick` has different values on every rerender**
1523 
1524```tsx
1525const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
1526 // ...
1527})
1528 
1529// Used without optional onClick
1530<UserAvatar />
1531```
1532 
1533**Correct: stable default value**
1534 
1535```tsx
1536const NOOP = () => {};
1537 
1538const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
1539 // ...
1540})
1541 
1542// Used without optional onClick
1543<UserAvatar />
1544```
1545 
1546### 5.6 Extract to Memoized Components
1547 
1548**Impact: MEDIUM (enables early returns)**
1549 
1550Extract expensive work into memoized components to enable early returns before computation.
1551 
1552**Incorrect: computes avatar even when loading**
1553 
1554```tsx
1555function Profile({ user, loading }: Props) {
1556 const avatar = useMemo(() => {
1557 const id = computeAvatarId(user)
1558 return <Avatar id={id} />
1559 }, [user])
1560 
1561 if (loading) return <Skeleton />
1562 return <div>{avatar}</div>
1563}
1564```
1565 
1566**Correct: skips computation when loading**
1567 
1568```tsx
1569const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
1570 const id = useMemo(() => computeAvatarId(user), [user])
1571 return <Avatar id={id} />
1572})
1573 
1574function Profile({ user, loading }: Props) {
1575 if (loading) return <Skeleton />
1576 return (
1577 <div>
1578 <UserAvatar user={user} />
1579 </div>
1580 )
1581}
1582```
1583 
1584**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
1585 
1586### 5.7 Narrow Effect Dependencies
1587 
1588**Impact: LOW (minimizes effect re-runs)**
1589 
1590Specify primitive dependencies instead of objects to minimize effect re-runs.
1591 
1592**Incorrect: re-runs on any user field change**
1593 
1594```tsx
1595useEffect(() => {
1596 console.log(user.id)
1597}, [user])
1598```
1599 
1600**Correct: re-runs only when id changes**
1601 
1602```tsx
1603useEffect(() => {
1604 console.log(user.id)
1605}, [user.id])
1606```
1607 
1608**For derived state, compute outside effect:**
1609 
1610```tsx
1611// Incorrect: runs on width=767, 766, 765...
1612useEffect(() => {
1613 if (width < 768) {
1614 enableMobileMode()
1615 }
1616}, [width])
1617 
1618// Correct: runs only on boolean transition
1619const isMobile = width < 768
1620useEffect(() => {
1621 if (isMobile) {
1622 enableMobileMode()
1623 }
1624}, [isMobile])
1625```
1626 
1627### 5.8 Put Interaction Logic in Event Handlers
1628 
1629**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**
1630 
1631If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.
1632 
1633**Incorrect: event modeled as state + effect**
1634 
1635```tsx
1636function Form() {
1637 const [submitted, setSubmitted] = useState(false)
1638 const theme = useContext(ThemeContext)
1639 
1640 useEffect(() => {
1641 if (submitted) {
1642 post('/api/register')
1643 showToast('Registered', theme)
1644 }
1645 }, [submitted, theme])
1646 
1647 return <button onClick={() => setSubmitted(true)}>Submit</button>
1648}
1649```
1650 
1651**Correct: do it in the handler**
1652 
1653```tsx
1654function Form() {
1655 const theme = useContext(ThemeContext)
1656 
1657 function handleSubmit() {
1658 post('/api/register')
1659 showToast('Registered', theme)
1660 }
1661 
1662 return <button onClick={handleSubmit}>Submit</button>
1663}
1664```
1665 
1666Reference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
1667 
1668### 5.9 Subscribe to Derived State
1669 
1670**Impact: MEDIUM (reduces re-render frequency)**
1671 
1672Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
1673 
1674**Incorrect: re-renders on every pixel change**
1675 
1676```tsx
1677function Sidebar() {
1678 const width = useWindowWidth() // updates continuously
1679 const isMobile = width < 768
1680 return <nav className={isMobile ? 'mobile' : 'desktop'} />
1681}
1682```
1683 
1684**Correct: re-renders only when boolean changes**
1685 
1686```tsx
1687function Sidebar() {
1688 const isMobile = useMediaQuery('(max-width: 767px)')
1689 return <nav className={isMobile ? 'mobile' : 'desktop'} />
1690}
1691```
1692 
1693### 5.10 Use Functional setState Updates
1694 
1695**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**
1696 
1697When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
1698 
1699**Incorrect: requires state as dependency**
1700 
1701```tsx
1702function TodoList() {
1703 const [items, setItems] = useState(initialItems)
1704
1705 // Callback must depend on items, recreated on every items change
1706 const addItems = useCallback((newItems: Item[]) => {
1707 setItems([...items, ...newItems])
1708 }, [items]) // ❌ items dependency causes recreations
1709
1710 // Risk of stale closure if dependency is forgotten
1711 const removeItem = useCallback((id: string) => {
1712 setItems(items.filter(item => item.id !== id))
1713 }, []) // ❌ Missing items dependency - will use stale items!
1714
1715 return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
1716}
1717```
1718 
1719The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
1720 
1721**Correct: stable callbacks, no stale closures**
1722 
1723```tsx
1724function TodoList() {
1725 const [items, setItems] = useState(initialItems)
1726
1727 // Stable callback, never recreated
1728 const addItems = useCallback((newItems: Item[]) => {
1729 setItems(curr => [...curr, ...newItems])
1730 }, []) // ✅ No dependencies needed
1731
1732 // Always uses latest state, no stale closure risk
1733 const removeItem = useCallback((id: string) => {
1734 setItems(curr => curr.filter(item => item.id !== id))
1735 }, []) // ✅ Safe and stable
1736
1737 return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
1738}
1739```
1740 
1741**Benefits:**
1742 
17431. **Stable callback references** - Callbacks don't need to be recreated when state changes
1744 
17452. **No stale closures** - Always operates on the latest state value
1746 
17473. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
1748 
17494. **Prevents bugs** - Eliminates the most common source of React closure bugs
1750 
1751**When to use functional updates:**
1752 
1753- Any setState that depends on the current state value
1754 
1755- Inside useCallback/useMemo when state is needed
1756 
1757- Event handlers that reference state
1758 
1759- Async operations that update state
1760 
1761**When direct updates are fine:**
1762 
1763- Setting state to a static value: `setCount(0)`
1764 
1765- Setting state from props/arguments only: `setName(newName)`
1766 
1767- State doesn't depend on previous value
1768 
1769**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
1770 
1771### 5.11 Use Lazy State Initialization
1772 
1773**Impact: MEDIUM (wasted computation on every render)**
1774 
1775Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
1776 
1777**Incorrect: runs on every render**
1778 
1779```tsx
1780function FilteredList({ items }: { items: Item[] }) {
1781 // buildSearchIndex() runs on EVERY render, even after initialization
1782 const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
1783 const [query, setQuery] = useState('')
1784
1785 // When query changes, buildSearchIndex runs again unnecessarily
1786 return <SearchResults index={searchIndex} query={query} />
1787}
1788 
1789function UserProfile() {
1790 // JSON.parse runs on every render
1791 const [settings, setSettings] = useState(
1792 JSON.parse(localStorage.getItem('settings') || '{}')
1793 )
1794
1795 return <SettingsForm settings={settings} onChange={setSettings} />
1796}
1797```
1798 
1799**Correct: runs only once**
1800 
1801```tsx
1802function FilteredList({ items }: { items: Item[] }) {
1803 // buildSearchIndex() runs ONLY on initial render
1804 const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
1805 const [query, setQuery] = useState('')
1806
1807 return <SearchResults index={searchIndex} query={query} />
1808}
1809 
1810function UserProfile() {
1811 // JSON.parse runs only on initial render
1812 const [settings, setSettings] = useState(() => {
1813 const stored = localStorage.getItem('settings')
1814 return stored ? JSON.parse(stored) : {}
1815 })
1816
1817 return <SettingsForm settings={settings} onChange={setSettings} />
1818}
1819```
1820 
1821Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
1822 
1823For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
1824 
1825### 5.12 Use Transitions for Non-Urgent Updates
1826 
1827**Impact: MEDIUM (maintains UI responsiveness)**
1828 
1829Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
1830 
1831**Incorrect: blocks UI on every scroll**
1832 
1833```tsx
1834function ScrollTracker() {
1835 const [scrollY, setScrollY] = useState(0)
1836 useEffect(() => {
1837 const handler = () => setScrollY(window.scrollY)
1838 window.addEventListener('scroll', handler, { passive: true })
1839 return () => window.removeEventListener('scroll', handler)
1840 }, [])
1841}
1842```
1843 
1844**Correct: non-blocking updates**
1845 
1846```tsx
1847import { startTransition } from 'react'
1848 
1849function ScrollTracker() {
1850 const [scrollY, setScrollY] = useState(0)
1851 useEffect(() => {
1852 const handler = () => {
1853 startTransition(() => setScrollY(window.scrollY))
1854 }
1855 window.addEventListener('scroll', handler, { passive: true })
1856 return () => window.removeEventListener('scroll', handler)
1857 }, [])
1858}
1859```
1860 
1861### 5.13 Use useRef for Transient Values
1862 
1863**Impact: MEDIUM (avoids unnecessary re-renders on frequent updates)**
1864 
1865When a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`. Keep component state for UI; use refs for temporary DOM-adjacent values. Updating a ref does not trigger a re-render.
1866 
1867**Incorrect: renders every update**
1868 
1869```tsx
1870function Tracker() {
1871 const [lastX, setLastX] = useState(0)
1872 
1873 useEffect(() => {
1874 const onMove = (e: MouseEvent) => setLastX(e.clientX)
1875 window.addEventListener('mousemove', onMove)
1876 return () => window.removeEventListener('mousemove', onMove)
1877 }, [])
1878 
1879 return (
1880 <div
1881 style={{
1882 position: 'fixed',
1883 top: 0,
1884 left: lastX,
1885 width: 8,
1886 height: 8,
1887 background: 'black',
1888 }}
1889 />
1890 )
1891}
1892```
1893 
1894**Correct: no re-render for tracking**
1895 
1896```tsx
1897function Tracker() {
1898 const lastXRef = useRef(0)
1899 const dotRef = useRef<HTMLDivElement>(null)
1900 
1901 useEffect(() => {
1902 const onMove = (e: MouseEvent) => {
1903 lastXRef.current = e.clientX
1904 const node = dotRef.current
1905 if (node) {
1906 node.style.transform = `translateX(${e.clientX}px)`
1907 }
1908 }
1909 window.addEventListener('mousemove', onMove)
1910 return () => window.removeEventListener('mousemove', onMove)
1911 }, [])
1912 
1913 return (
1914 <div
1915 ref={dotRef}
1916 style={{
1917 position: 'fixed',
1918 top: 0,
1919 left: 0,
1920 width: 8,
1921 height: 8,
1922 background: 'black',
1923 transform: 'translateX(0px)',
1924 }}
1925 />
1926 )
1927}
1928```
1929 
1930---
1931 
1932## 6. Rendering Performance
1933 
1934**Impact: MEDIUM**
1935 
1936Optimizing the rendering process reduces the work the browser needs to do.
1937 
1938### 6.1 Animate SVG Wrapper Instead of SVG Element
1939 
1940**Impact: LOW (enables hardware acceleration)**
1941 
1942Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.
1943 
1944**Incorrect: animating SVG directly - no hardware acceleration**
1945 
1946```tsx
1947function LoadingSpinner() {
1948 return (
1949 <svg
1950 className="animate-spin"
1951 width="24"
1952 height="24"
1953 viewBox="0 0 24 24"
1954 >
1955 <circle cx="12" cy="12" r="10" stroke="currentColor" />
1956 </svg>
1957 )
1958}
1959```
1960 
1961**Correct: animating wrapper div - hardware accelerated**
1962 
1963```tsx
1964function LoadingSpinner() {
1965 return (
1966 <div className="animate-spin">
1967 <svg
1968 width="24"
1969 height="24"
1970 viewBox="0 0 24 24"
1971 >
1972 <circle cx="12" cy="12" r="10" stroke="currentColor" />
1973 </svg>
1974 </div>
1975 )
1976}
1977```
1978 
1979This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
1980 
1981### 6.2 CSS content-visibility for Long Lists
1982 
1983**Impact: HIGH (faster initial render)**
1984 
1985Apply `content-visibility: auto` to defer off-screen rendering.
1986 
1987**CSS:**
1988 
1989```css
1990.message-item {
1991 content-visibility: auto;
1992 contain-intrinsic-size: 0 80px;
1993}
1994```
1995 
1996**Example:**
1997 
1998```tsx
1999function MessageList({ messages }: { messages: Message[] }) {
2000 return (
2001 <div className="overflow-y-auto h-screen">
2002 {messages.map(msg => (
2003 <div key={msg.id} className="message-item">
2004 <Avatar user={msg.author} />
2005 <div>{msg.content}</div>
2006 </div>
2007 ))}
2008 </div>
2009 )
2010}
2011```
2012 
2013For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
2014 
2015### 6.3 Hoist Static JSX Elements
2016 
2017**Impact: LOW (avoids re-creation)**
2018 
2019Extract static JSX outside components to avoid re-creation.
2020 
2021**Incorrect: recreates element every render**
2022 
2023```tsx
2024function LoadingSkeleton() {
2025 return <div className="animate-pulse h-20 bg-gray-200" />
2026}
2027 
2028function Container() {
2029 return (
2030 <div>
2031 {loading && <LoadingSkeleton />}
2032 </div>
2033 )
2034}
2035```
2036 
2037**Correct: reuses same element**
2038 
2039```tsx
2040const loadingSkeleton = (
2041 <div className="animate-pulse h-20 bg-gray-200" />
2042)
2043 
2044function Container() {
2045 return (
2046 <div>
2047 {loading && loadingSkeleton}
2048 </div>
2049 )
2050}
2051```
2052 
2053This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
2054 
2055**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
2056 
2057### 6.4 Optimize SVG Precision
2058 
2059**Impact: LOW (reduces file size)**
2060 
2061Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
2062 
2063**Incorrect: excessive precision**
2064 
2065```svg
2066<path d="M 10.293847 20.847362 L 30.938472 40.192837" />
2067```
2068 
2069**Correct: 1 decimal place**
2070 
2071```svg
2072<path d="M 10.3 20.8 L 30.9 40.2" />
2073```
2074 
2075**Automate with SVGO:**
2076 
2077```bash
2078npx svgo --precision=1 --multipass icon.svg
2079```
2080 
2081### 6.5 Prevent Hydration Mismatch Without Flickering
2082 
2083**Impact: MEDIUM (avoids visual flicker and hydration errors)**
2084 
2085When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
2086 
2087**Incorrect: breaks SSR**
2088 
2089```tsx
2090function ThemeWrapper({ children }: { children: ReactNode }) {
2091 // localStorage is not available on server - throws error
2092 const theme = localStorage.getItem('theme') || 'light'
2093
2094 return (
2095 <div className={theme}>
2096 {children}
2097 </div>
2098 )
2099}
2100```
2101 
2102Server-side rendering will fail because `localStorage` is undefined.
2103 
2104**Incorrect: visual flickering**
2105 
2106```tsx
2107function ThemeWrapper({ children }: { children: ReactNode }) {
2108 const [theme, setTheme] = useState('light')
2109
2110 useEffect(() => {
2111 // Runs after hydration - causes visible flash
2112 const stored = localStorage.getItem('theme')
2113 if (stored) {
2114 setTheme(stored)
2115 }
2116 }, [])
2117
2118 return (
2119 <div className={theme}>
2120 {children}
2121 </div>
2122 )
2123}
2124```
2125 
2126Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
2127 
2128**Correct: no flicker, no hydration mismatch**
2129 
2130```tsx
2131function ThemeWrapper({ children }: { children: ReactNode }) {
2132 return (
2133 <>
2134 <div id="theme-wrapper">
2135 {children}
2136 </div>
2137 <script
2138 dangerouslySetInnerHTML={{
2139 __html: `
2140 (function() {
2141 try {
2142 var theme = localStorage.getItem('theme') || 'light';
2143 var el = document.getElementById('theme-wrapper');
2144 if (el) el.className = theme;
2145 } catch (e) {}
2146 })();
2147 `,
2148 }}
2149 />
2150 </>
2151 )
2152}
2153```
2154 
2155The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
2156 
2157This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
2158 
2159### 6.6 Suppress Expected Hydration Mismatches
2160 
2161**Impact: LOW-MEDIUM (avoids noisy hydration warnings for known differences)**
2162 
2163In SSR frameworks (e.g., Next.js), some values are intentionally different on server vs client (random IDs, dates, locale/timezone formatting). For these *expected* mismatches, wrap the dynamic text in an element with `suppressHydrationWarning` to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.
2164 
2165**Incorrect: known mismatch warnings**
2166 
2167```tsx
2168function Timestamp() {
2169 return <span>{new Date().toLocaleString()}</span>
2170}
2171```
2172 
2173**Correct: suppress expected mismatch only**
2174 
2175```tsx
2176function Timestamp() {
2177 return (
2178 <span suppressHydrationWarning>
2179 {new Date().toLocaleString()}
2180 </span>
2181 )
2182}
2183```
2184 
2185### 6.7 Use Activity Component for Show/Hide
2186 
2187**Impact: MEDIUM (preserves state/DOM)**
2188 
2189Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.
2190 
2191**Usage:**
2192 
2193```tsx
2194import { Activity } from 'react'
2195 
2196function Dropdown({ isOpen }: Props) {
2197 return (
2198 <Activity mode={isOpen ? 'visible' : 'hidden'}>
2199 <ExpensiveMenu />
2200 </Activity>
2201 )
2202}
2203```
2204 
2205Avoids expensive re-renders and state loss.
2206 
2207### 6.8 Use defer or async on Script Tags
2208 
2209**Impact: HIGH (eliminates render-blocking)**
2210 
2211Script tags without `defer` or `async` block HTML parsing while the script downloads and executes. This delays First Contentful Paint and Time to Interactive.
2212 
2213- **`defer`**: Downloads in parallel, executes after HTML parsing completes, maintains execution order
2214 
2215- **`async`**: Downloads in parallel, executes immediately when ready, no guaranteed order
2216 
2217Use `defer` for scripts that depend on DOM or other scripts. Use `async` for independent scripts like analytics.
2218 
2219**Incorrect: blocks rendering**
2220 
2221```tsx
2222export default function Document() {
2223 return (
2224 <html>
2225 <head>
2226 <script src="https://example.com/analytics.js" />
2227 <script src="/scripts/utils.js" />
2228 </head>
2229 <body>{/* content */}</body>
2230 </html>
2231 )
2232}
2233```
2234 
2235**Correct: non-blocking**
2236 
2237```tsx
2238import Script from 'next/script'
2239 
2240export default function Page() {
2241 return (
2242 <>
2243 <Script src="https://example.com/analytics.js" strategy="afterInteractive" />
2244 <Script src="/scripts/utils.js" strategy="beforeInteractive" />
2245 </>
2246 )
2247}
2248```
2249 
2250**Note:** In Next.js, prefer the `next/script` component with `strategy` prop instead of raw script tags:
2251 
2252Reference: [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer)
2253 
2254### 6.9 Use Explicit Conditional Rendering
2255 
2256**Impact: LOW (prevents rendering 0 or NaN)**
2257 
2258Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
2259 
2260**Incorrect: renders "0" when count is 0**
2261 
2262```tsx
2263function Badge({ count }: { count: number }) {
2264 return (
2265 <div>
2266 {count && <span className="badge">{count}</span>}
2267 </div>
2268 )
2269}
2270 
2271// When count = 0, renders: <div>0</div>
2272// When count = 5, renders: <div><span class="badge">5</span></div>
2273```
2274 
2275**Correct: renders nothing when count is 0**
2276 
2277```tsx
2278function Badge({ count }: { count: number }) {
2279 return (
2280 <div>
2281 {count > 0 ? <span className="badge">{count}</span> : null}
2282 </div>
2283 )
2284}
2285 
2286// When count = 0, renders: <div></div>
2287// When count = 5, renders: <div><span class="badge">5</span></div>
2288```
2289 
2290### 6.10 Use React DOM Resource Hints
2291 
2292**Impact: HIGH (reduces load time for critical resources)**
2293 
2294React DOM provides APIs to hint the browser about resources it will need. These are especially useful in server components to start loading resources before the client even receives the HTML.
2295 
2296- **`prefetchDNS(href)`**: Resolve DNS for a domain you expect to connect to
2297 
2298- **`preconnect(href)`**: Establish connection (DNS + TCP + TLS) to a server
2299 
2300- **`preload(href, options)`**: Fetch a resource (stylesheet, font, script, image) you'll use soon
2301 
2302- **`preloadModule(href)`**: Fetch an ES module you'll use soon
2303 
2304- **`preinit(href, options)`**: Fetch and evaluate a stylesheet or script
2305 
2306- **`preinitModule(href)`**: Fetch and evaluate an ES module
2307 
2308**Example: preconnect to third-party APIs**
2309 
2310```tsx
2311import { preconnect, prefetchDNS } from 'react-dom'
2312 
2313export default function App() {
2314 prefetchDNS('https://analytics.example.com')
2315 preconnect('https://api.example.com')
2316 
2317 return <main>{/* content */}</main>
2318}
2319```
2320 
2321**Example: preload critical fonts and styles**
2322 
2323```tsx
2324import { preload, preinit } from 'react-dom'
2325 
2326export default function RootLayout({ children }) {
2327 // Preload font file
2328 preload('/fonts/inter.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' })
2329 
2330 // Fetch and apply critical stylesheet immediately
2331 preinit('/styles/critical.css', { as: 'style' })
2332 
2333 return (
2334 <html>
2335 <body>{children}</body>
2336 </html>
2337 )
2338}
2339```
2340 
2341**Example: preload modules for code-split routes**
2342 
2343```tsx
2344import { preloadModule, preinitModule } from 'react-dom'
2345 
2346function Navigation() {
2347 const preloadDashboard = () => {
2348 preloadModule('/dashboard.js', { as: 'script' })
2349 }
2350 
2351 return (
2352 <nav>
2353 <a href="/dashboard" onMouseEnter={preloadDashboard}>
2354 Dashboard
2355 </a>
2356 </nav>
2357 )
2358}
2359```
2360 
2361**When to use each:**
2362 
2363| API | Use case |
2364 
2365|-----|----------|
2366 
2367| `prefetchDNS` | Third-party domains you'll connect to later |
2368 
2369| `preconnect` | APIs or CDNs you'll fetch from immediately |
2370 
2371| `preload` | Critical resources needed for current page |
2372 
2373| `preloadModule` | JS modules for likely next navigation |
2374 
2375| `preinit` | Stylesheets/scripts that must execute early |
2376 
2377| `preinitModule` | ES modules that must execute early |
2378 
2379Reference: [https://react.dev/reference/react-dom#resource-preloading-apis](https://react.dev/reference/react-dom#resource-preloading-apis)
2380 
2381### 6.11 Use useTransition Over Manual Loading States
2382 
2383**Impact: LOW (reduces re-renders and improves code clarity)**
2384 
2385Use `useTransition` instead of manual `useState` for loading states. This provides built-in `isPending` state and automatically manages transitions.
2386 
2387**Incorrect: manual loading state**
2388 
2389```tsx
2390function SearchResults() {
2391 const [query, setQuery] = useState('')
2392 const [results, setResults] = useState([])
2393 const [isLoading, setIsLoading] = useState(false)
2394 
2395 const handleSearch = async (value: string) => {
2396 setIsLoading(true)
2397 setQuery(value)
2398 const data = await fetchResults(value)
2399 setResults(data)
2400 setIsLoading(false)
2401 }
2402 
2403 return (
2404 <>
2405 <input onChange={(e) => handleSearch(e.target.value)} />
2406 {isLoading && <Spinner />}
2407 <ResultsList results={results} />
2408 </>
2409 )
2410}
2411```
2412 
2413**Correct: useTransition with built-in pending state**
2414 
2415```tsx
2416import { useTransition, useState } from 'react'
2417 
2418function SearchResults() {
2419 const [query, setQuery] = useState('')
2420 const [results, setResults] = useState([])
2421 const [isPending, startTransition] = useTransition()
2422 
2423 const handleSearch = (value: string) => {
2424 setQuery(value) // Update input immediately
2425
2426 startTransition(async () => {
2427 // Fetch and update results
2428 const data = await fetchResults(value)
2429 setResults(data)
2430 })
2431 }
2432 
2433 return (
2434 <>
2435 <input onChange={(e) => handleSearch(e.target.value)} />
2436 {isPending && <Spinner />}
2437 <ResultsList results={results} />
2438 </>
2439 )
2440}
2441```
2442 
2443**Benefits:**
2444 
2445- **Automatic pending state**: No need to manually manage `setIsLoading(true/false)`
2446 
2447- **Error resilience**: Pending state correctly resets even if the transition throws
2448 
2449- **Better responsiveness**: Keeps the UI responsive during updates
2450 
2451- **Interrupt handling**: New transitions automatically cancel pending ones
2452 
2453Reference: [https://react.dev/reference/react/useTransition](https://react.dev/reference/react/useTransition)
2454 
2455---
2456 
2457## 7. JavaScript Performance
2458 
2459**Impact: LOW-MEDIUM**
2460 
2461Micro-optimizations for hot paths can add up to meaningful improvements.
2462 
2463### 7.1 Avoid Layout Thrashing
2464 
2465**Impact: MEDIUM (prevents forced synchronous layouts and reduces performance bottlenecks)**
2466 
2467Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
2468 
2469**This is OK: browser batches style changes**
2470 
2471```typescript
2472function updateElementStyles(element: HTMLElement) {
2473 // Each line invalidates style, but browser batches the recalculation
2474 element.style.width = '100px'
2475 element.style.height = '200px'
2476 element.style.backgroundColor = 'blue'
2477 element.style.border = '1px solid black'
2478}
2479```
2480 
2481**Incorrect: interleaved reads and writes force reflows**
2482 
2483```typescript
2484function layoutThrashing(element: HTMLElement) {
2485 element.style.width = '100px'
2486 const width = element.offsetWidth // Forces reflow
2487 element.style.height = '200px'
2488 const height = element.offsetHeight // Forces another reflow
2489}
2490```
2491 
2492**Correct: batch writes, then read once**
2493 
2494```typescript
2495function updateElementStyles(element: HTMLElement) {
2496 // Batch all writes together
2497 element.style.width = '100px'
2498 element.style.height = '200px'
2499 element.style.backgroundColor = 'blue'
2500 element.style.border = '1px solid black'
2501
2502 // Read after all writes are done (single reflow)
2503 const { width, height } = element.getBoundingClientRect()
2504}
2505```
2506 
2507**Correct: batch reads, then writes**
2508 
2509```typescript
2510function updateElementStyles(element: HTMLElement) {
2511 element.classList.add('highlighted-box')
2512
2513 const { width, height } = element.getBoundingClientRect()
2514}
2515```
2516 
2517**Better: use CSS classes**
2518 
2519**React example:**
2520 
2521```tsx
2522// Incorrect: interleaving style changes with layout queries
2523function Box({ isHighlighted }: { isHighlighted: boolean }) {
2524 const ref = useRef<HTMLDivElement>(null)
2525
2526 useEffect(() => {
2527 if (ref.current && isHighlighted) {
2528 ref.current.style.width = '100px'
2529 const width = ref.current.offsetWidth // Forces layout
2530 ref.current.style.height = '200px'
2531 }
2532 }, [isHighlighted])
2533
2534 return <div ref={ref}>Content</div>
2535}
2536 
2537// Correct: toggle class
2538function Box({ isHighlighted }: { isHighlighted: boolean }) {
2539 return (
2540 <div className={isHighlighted ? 'highlighted-box' : ''}>
2541 Content
2542 </div>
2543 )
2544}
2545```
2546 
2547Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
2548 
2549See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.
2550 
2551### 7.2 Build Index Maps for Repeated Lookups
2552 
2553**Impact: LOW-MEDIUM (1M ops to 2K ops)**
2554 
2555Multiple `.find()` calls by the same key should use a Map.
2556 
2557**Incorrect (O(n) per lookup):**
2558 
2559```typescript
2560function processOrders(orders: Order[], users: User[]) {
2561 return orders.map(order => ({
2562 ...order,
2563 user: users.find(u => u.id === order.userId)
2564 }))
2565}
2566```
2567 
2568**Correct (O(1) per lookup):**
2569 
2570```typescript
2571function processOrders(orders: Order[], users: User[]) {
2572 const userById = new Map(users.map(u => [u.id, u]))
2573 
2574 return orders.map(order => ({
2575 ...order,
2576 user: userById.get(order.userId)
2577 }))
2578}
2579```
2580 
2581Build map once (O(n)), then all lookups are O(1).
2582 
2583For 1000 orders × 1000 users: 1M ops → 2K ops.
2584 
2585### 7.3 Cache Property Access in Loops
2586 
2587**Impact: LOW-MEDIUM (reduces lookups)**
2588 
2589Cache object property lookups in hot paths.
2590 
2591**Incorrect: 3 lookups × N iterations**
2592 
2593```typescript
2594for (let i = 0; i < arr.length; i++) {
2595 process(obj.config.settings.value)
2596}
2597```
2598 
2599**Correct: 1 lookup total**
2600 
2601```typescript
2602const value = obj.config.settings.value
2603const len = arr.length
2604for (let i = 0; i < len; i++) {
2605 process(value)
2606}
2607```
2608 
2609### 7.4 Cache Repeated Function Calls
2610 
2611**Impact: MEDIUM (avoid redundant computation)**
2612 
2613Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
2614 
2615**Incorrect: redundant computation**
2616 
2617```typescript
2618function ProjectList({ projects }: { projects: Project[] }) {
2619 return (
2620 <div>
2621 {projects.map(project => {
2622 // slugify() called 100+ times for same project names
2623 const slug = slugify(project.name)
2624
2625 return <ProjectCard key={project.id} slug={slug} />
2626 })}
2627 </div>
2628 )
2629}
2630```
2631 
2632**Correct: cached results**
2633 
2634```typescript
2635// Module-level cache
2636const slugifyCache = new Map<string, string>()
2637 
2638function cachedSlugify(text: string): string {
2639 if (slugifyCache.has(text)) {
2640 return slugifyCache.get(text)!
2641 }
2642 const result = slugify(text)
2643 slugifyCache.set(text, result)
2644 return result
2645}
2646 
2647function ProjectList({ projects }: { projects: Project[] }) {
2648 return (
2649 <div>
2650 {projects.map(project => {
2651 // Computed only once per unique project name
2652 const slug = cachedSlugify(project.name)
2653
2654 return <ProjectCard key={project.id} slug={slug} />
2655 })}
2656 </div>
2657 )
2658}
2659```
2660 
2661**Simpler pattern for single-value functions:**
2662 
2663```typescript
2664let isLoggedInCache: boolean | null = null
2665 
2666function isLoggedIn(): boolean {
2667 if (isLoggedInCache !== null) {
2668 return isLoggedInCache
2669 }
2670
2671 isLoggedInCache = document.cookie.includes('auth=')
2672 return isLoggedInCache
2673}
2674 
2675// Clear cache when auth changes
2676function onAuthChange() {
2677 isLoggedInCache = null
2678}
2679```
2680 
2681Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
2682 
2683Reference: [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
2684 
2685### 7.5 Cache Storage API Calls
2686 
2687**Impact: LOW-MEDIUM (reduces expensive I/O)**
2688 
2689`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
2690 
2691**Incorrect: reads storage on every call**
2692 
2693```typescript
2694function getTheme() {
2695 return localStorage.getItem('theme') ?? 'light'
2696}
2697// Called 10 times = 10 storage reads
2698```
2699 
2700**Correct: Map cache**
2701 
2702```typescript
2703const storageCache = new Map<string, string | null>()
2704 
2705function getLocalStorage(key: string) {
2706 if (!storageCache.has(key)) {
2707 storageCache.set(key, localStorage.getItem(key))
2708 }
2709 return storageCache.get(key)
2710}
2711 
2712function setLocalStorage(key: string, value: string) {
2713 localStorage.setItem(key, value)
2714 storageCache.set(key, value) // keep cache in sync
2715}
2716```
2717 
2718Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
2719 
2720**Cookie caching:**
2721 
2722```typescript
2723let cookieCache: Record<string, string> | null = null
2724 
2725function getCookie(name: string) {
2726 if (!cookieCache) {
2727 cookieCache = Object.fromEntries(
2728 document.cookie.split('; ').map(c => c.split('='))
2729 )
2730 }
2731 return cookieCache[name]
2732}
2733```
2734 
2735**Important: invalidate on external changes**
2736 
2737```typescript
2738window.addEventListener('storage', (e) => {
2739 if (e.key) storageCache.delete(e.key)
2740})
2741 
2742document.addEventListener('visibilitychange', () => {
2743 if (document.visibilityState === 'visible') {
2744 storageCache.clear()
2745 }
2746})
2747```
2748 
2749If storage can change externally (another tab, server-set cookies), invalidate cache:
2750 
2751### 7.6 Combine Multiple Array Iterations
2752 
2753**Impact: LOW-MEDIUM (reduces iterations)**
2754 
2755Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
2756 
2757**Incorrect: 3 iterations**
2758 
2759```typescript
2760const admins = users.filter(u => u.isAdmin)
2761const testers = users.filter(u => u.isTester)
2762const inactive = users.filter(u => !u.isActive)
2763```
2764 
2765**Correct: 1 iteration**
2766 
2767```typescript
2768const admins: User[] = []
2769const testers: User[] = []
2770const inactive: User[] = []
2771 
2772for (const user of users) {
2773 if (user.isAdmin) admins.push(user)
2774 if (user.isTester) testers.push(user)
2775 if (!user.isActive) inactive.push(user)
2776}
2777```
2778 
2779### 7.7 Early Length Check for Array Comparisons
2780 
2781**Impact: MEDIUM-HIGH (avoids expensive operations when lengths differ)**
2782 
2783When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
2784 
2785In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
2786 
2787**Incorrect: always runs expensive comparison**
2788 
2789```typescript
2790function hasChanges(current: string[], original: string[]) {
2791 // Always sorts and joins, even when lengths differ
2792 return current.sort().join() !== original.sort().join()
2793}
2794```
2795 
2796Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
2797 
2798**Correct (O(1) length check first):**
2799 
2800```typescript
2801function hasChanges(current: string[], original: string[]) {
2802 // Early return if lengths differ
2803 if (current.length !== original.length) {
2804 return true
2805 }
2806 // Only sort when lengths match
2807 const currentSorted = current.toSorted()
2808 const originalSorted = original.toSorted()
2809 for (let i = 0; i < currentSorted.length; i++) {
2810 if (currentSorted[i] !== originalSorted[i]) {
2811 return true
2812 }
2813 }
2814 return false
2815}
2816```
2817 
2818This new approach is more efficient because:
2819 
2820- It avoids the overhead of sorting and joining the arrays when lengths differ
2821 
2822- It avoids consuming memory for the joined strings (especially important for large arrays)
2823 
2824- It avoids mutating the original arrays
2825 
2826- It returns early when a difference is found
2827 
2828### 7.8 Early Return from Functions
2829 
2830**Impact: LOW-MEDIUM (avoids unnecessary computation)**
2831 
2832Return early when result is determined to skip unnecessary processing.
2833 
2834**Incorrect: processes all items even after finding answer**
2835 
2836```typescript
2837function validateUsers(users: User[]) {
2838 let hasError = false
2839 let errorMessage = ''
2840
2841 for (const user of users) {
2842 if (!user.email) {
2843 hasError = true
2844 errorMessage = 'Email required'
2845 }
2846 if (!user.name) {
2847 hasError = true
2848 errorMessage = 'Name required'
2849 }
2850 // Continues checking all users even after error found
2851 }
2852
2853 return hasError ? { valid: false, error: errorMessage } : { valid: true }
2854}
2855```
2856 
2857**Correct: returns immediately on first error**
2858 
2859```typescript
2860function validateUsers(users: User[]) {
2861 for (const user of users) {
2862 if (!user.email) {
2863 return { valid: false, error: 'Email required' }
2864 }
2865 if (!user.name) {
2866 return { valid: false, error: 'Name required' }
2867 }
2868 }
2869 
2870 return { valid: true }
2871}
2872```
2873 
2874### 7.9 Hoist RegExp Creation
2875 
2876**Impact: LOW-MEDIUM (avoids recreation)**
2877 
2878Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
2879 
2880**Incorrect: new RegExp every render**
2881 
2882```tsx
2883function Highlighter({ text, query }: Props) {
2884 const regex = new RegExp(`(${query})`, 'gi')
2885 const parts = text.split(regex)
2886 return <>{parts.map((part, i) => ...)}</>
2887}
2888```
2889 
2890**Correct: memoize or hoist**
2891 
2892```tsx
2893const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
2894 
2895function Highlighter({ text, query }: Props) {
2896 const regex = useMemo(
2897 () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
2898 [query]
2899 )
2900 const parts = text.split(regex)
2901 return <>{parts.map((part, i) => ...)}</>
2902}
2903```
2904 
2905**Warning: global regex has mutable state**
2906 
2907```typescript
2908const regex = /foo/g
2909regex.test('foo') // true, lastIndex = 3
2910regex.test('foo') // false, lastIndex = 0
2911```
2912 
2913Global regex (`/g`) has mutable `lastIndex` state:
2914 
2915### 7.10 Use flatMap to Map and Filter in One Pass
2916 
2917**Impact: LOW-MEDIUM (eliminates intermediate array)**
2918 
2919Chaining `.map().filter(Boolean)` creates an intermediate array and iterates twice. Use `.flatMap()` to transform and filter in a single pass.
2920 
2921**Incorrect: 2 iterations, intermediate array**
2922 
2923```typescript
2924const userNames = users
2925 .map(user => user.isActive ? user.name : null)
2926 .filter(Boolean)
2927```
2928 
2929**Correct: 1 iteration, no intermediate array**
2930 
2931```typescript
2932const userNames = users.flatMap(user =>
2933 user.isActive ? [user.name] : []
2934)
2935```
2936 
2937**More examples:**
2938 
2939```typescript
2940// Extract valid emails from responses
2941// Before
2942const emails = responses
2943 .map(r => r.success ? r.data.email : null)
2944 .filter(Boolean)
2945 
2946// After
2947const emails = responses.flatMap(r =>
2948 r.success ? [r.data.email] : []
2949)
2950 
2951// Parse and filter valid numbers
2952// Before
2953const numbers = strings
2954 .map(s => parseInt(s, 10))
2955 .filter(n => !isNaN(n))
2956 
2957// After
2958const numbers = strings.flatMap(s => {
2959 const n = parseInt(s, 10)
2960 return isNaN(n) ? [] : [n]
2961})
2962```
2963 
2964**When to use:**
2965 
2966- Transforming items while filtering some out
2967 
2968- Conditional mapping where some inputs produce no output
2969 
2970- Parsing/validating where invalid inputs should be skipped
2971 
2972### 7.11 Use Loop for Min/Max Instead of Sort
2973 
2974**Impact: LOW (O(n) instead of O(n log n))**
2975 
2976Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
2977 
2978**Incorrect (O(n log n) - sort to find latest):**
2979 
2980```typescript
2981interface Project {
2982 id: string
2983 name: string
2984 updatedAt: number
2985}
2986 
2987function getLatestProject(projects: Project[]) {
2988 const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
2989 return sorted[0]
2990}
2991```
2992 
2993Sorts the entire array just to find the maximum value.
2994 
2995**Incorrect (O(n log n) - sort for oldest and newest):**
2996 
2997```typescript
2998function getOldestAndNewest(projects: Project[]) {
2999 const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
3000 return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
3001}
3002```
3003 
3004Still sorts unnecessarily when only min/max are needed.
3005 
3006**Correct (O(n) - single loop):**
3007 
3008```typescript
3009function getLatestProject(projects: Project[]) {
3010 if (projects.length === 0) return null
3011
3012 let latest = projects[0]
3013
3014 for (let i = 1; i < projects.length; i++) {
3015 if (projects[i].updatedAt > latest.updatedAt) {
3016 latest = projects[i]
3017 }
3018 }
3019
3020 return latest
3021}
3022 
3023function getOldestAndNewest(projects: Project[]) {
3024 if (projects.length === 0) return { oldest: null, newest: null }
3025
3026 let oldest = projects[0]
3027 let newest = projects[0]
3028
3029 for (let i = 1; i < projects.length; i++) {
3030 if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
3031 if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
3032 }
3033
3034 return { oldest, newest }
3035}
3036```
3037 
3038Single pass through the array, no copying, no sorting.
3039 
3040**Alternative: Math.min/Math.max for small arrays**
3041 
3042```typescript
3043const numbers = [5, 2, 8, 1, 9]
3044const min = Math.min(...numbers)
3045const max = Math.max(...numbers)
3046```
3047 
3048This works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.
3049 
3050### 7.12 Use Set/Map for O(1) Lookups
3051 
3052**Impact: LOW-MEDIUM (O(n) to O(1))**
3053 
3054Convert arrays to Set/Map for repeated membership checks.
3055 
3056**Incorrect (O(n) per check):**
3057 
3058```typescript
3059const allowedIds = ['a', 'b', 'c', ...]
3060items.filter(item => allowedIds.includes(item.id))
3061```
3062 
3063**Correct (O(1) per check):**
3064 
3065```typescript
3066const allowedIds = new Set(['a', 'b', 'c', ...])
3067items.filter(item => allowedIds.has(item.id))
3068```
3069 
3070### 7.13 Use toSorted() Instead of sort() for Immutability
3071 
3072**Impact: MEDIUM-HIGH (prevents mutation bugs in React state)**
3073 
3074`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
3075 
3076**Incorrect: mutates original array**
3077 
3078```typescript
3079function UserList({ users }: { users: User[] }) {
3080 // Mutates the users prop array!
3081 const sorted = useMemo(
3082 () => users.sort((a, b) => a.name.localeCompare(b.name)),
3083 [users]
3084 )
3085 return <div>{sorted.map(renderUser)}</div>
3086}
3087```
3088 
3089**Correct: creates new array**
3090 
3091```typescript
3092function UserList({ users }: { users: User[] }) {
3093 // Creates new sorted array, original unchanged
3094 const sorted = useMemo(
3095 () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
3096 [users]
3097 )
3098 return <div>{sorted.map(renderUser)}</div>
3099}
3100```
3101 
3102**Why this matters in React:**
3103 
31041. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
3105 
31062. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
3107 
3108**Browser support: fallback for older browsers**
3109 
3110```typescript
3111// Fallback for older browsers
3112const sorted = [...items].sort((a, b) => a.value - b.value)
3113```
3114 
3115`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
3116 
3117**Other immutable array methods:**
3118 
3119- `.toSorted()` - immutable sort
3120 
3121- `.toReversed()` - immutable reverse
3122 
3123- `.toSpliced()` - immutable splice
3124 
3125- `.with()` - immutable element replacement
3126 
3127---
3128 
3129## 8. Advanced Patterns
3130 
3131**Impact: LOW**
3132 
3133Advanced patterns for specific cases that require careful implementation.
3134 
3135### 8.1 Initialize App Once, Not Per Mount
3136 
3137**Impact: LOW-MEDIUM (avoids duplicate init in development)**
3138 
3139Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
3140 
3141**Incorrect: runs twice in dev, re-runs on remount**
3142 
3143```tsx
3144function Comp() {
3145 useEffect(() => {
3146 loadFromStorage()
3147 checkAuthToken()
3148 }, [])
3149 
3150 // ...
3151}
3152```
3153 
3154**Correct: once per app load**
3155 
3156```tsx
3157let didInit = false
3158 
3159function Comp() {
3160 useEffect(() => {
3161 if (didInit) return
3162 didInit = true
3163 loadFromStorage()
3164 checkAuthToken()
3165 }, [])
3166 
3167 // ...
3168}
3169```
3170 
3171Reference: [https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
3172 
3173### 8.2 Store Event Handlers in Refs
3174 
3175**Impact: LOW (stable subscriptions)**
3176 
3177Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
3178 
3179**Incorrect: re-subscribes on every render**
3180 
3181```tsx
3182function useWindowEvent(event: string, handler: (e) => void) {
3183 useEffect(() => {
3184 window.addEventListener(event, handler)
3185 return () => window.removeEventListener(event, handler)
3186 }, [event, handler])
3187}
3188```
3189 
3190**Correct: stable subscription**
3191 
3192```tsx
3193import { useEffectEvent } from 'react'
3194 
3195function useWindowEvent(event: string, handler: (e) => void) {
3196 const onEvent = useEffectEvent(handler)
3197 
3198 useEffect(() => {
3199 window.addEventListener(event, onEvent)
3200 return () => window.removeEventListener(event, onEvent)
3201 }, [event])
3202}
3203```
3204 
3205**Alternative: use `useEffectEvent` if you're on latest React:**
3206 
3207`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
3208 
3209### 8.3 useEffectEvent for Stable Callback Refs
3210 
3211**Impact: LOW (prevents effect re-runs)**
3212 
3213Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
3214 
3215**Incorrect: effect re-runs on every callback change**
3216 
3217```tsx
3218function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
3219 const [query, setQuery] = useState('')
3220 
3221 useEffect(() => {
3222 const timeout = setTimeout(() => onSearch(query), 300)
3223 return () => clearTimeout(timeout)
3224 }, [query, onSearch])
3225}
3226```
3227 
3228**Correct: using React's useEffectEvent**
3229 
3230```tsx
3231import { useEffectEvent } from 'react';
3232 
3233function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
3234 const [query, setQuery] = useState('')
3235 const onSearchEvent = useEffectEvent(onSearch)
3236 
3237 useEffect(() => {
3238 const timeout = setTimeout(() => onSearchEvent(query), 300)
3239 return () => clearTimeout(timeout)
3240 }, [query])
3241}
3242```
3243 
3244---
3245 
3246## References
3247 
32481. [https://react.dev](https://react.dev)
32492. [https://nextjs.org](https://nextjs.org)
32503. [https://swr.vercel.app](https://swr.vercel.app)
32514. [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
32525. [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
32536. [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
32547. [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
3255 

Commands it names

  • node.style.transform = `translateX(${e.clientX}px)`
  • npx svgo --precision=1 --multipass icon.svg

Sections

  • React Best Practices
  • Abstract
  • Table of Contents
  • 1. Eliminating Waterfalls
  • 1.1 Defer Await Until Needed
  • 1.2 Dependency-Based Parallelization
  • 1.3 Prevent Waterfall Chains in API Routes
  • 1.4 Promise.all() for Independent Operations
  • 1.5 Strategic Suspense Boundaries
  • 2. Bundle Size Optimization
  • 2.1 Avoid Barrel File Imports
  • 2.2 Conditional Module Loading
  • 2.3 Defer Non-Critical Third-Party Libraries
  • 2.4 Dynamic Imports for Heavy Components
  • 2.5 Preload Based on User Intent
  • 3. Server-Side Performance
  • 3.1 Authenticate Server Actions Like API Routes
  • 3.2 Avoid Duplicate Serialization in RSC Props
  • 3.3 Cross-Request LRU Caching
  • 3.4 Hoist Static I/O to Module Level
  • 3.5 Minimize Serialization at RSC Boundaries
  • 3.6 Parallel Data Fetching with Component Composition
  • 3.7 Per-Request Deduplication with React.cache()
  • 3.8 Use after() for Non-Blocking Operations
  • 4. Client-Side Data Fetching
  • 4.1 Deduplicate Global Event Listeners
  • 4.2 Use Passive Event Listeners for Scrolling Performance
  • 4.3 Use SWR for Automatic Deduplication
  • 4.4 Version and Minimize localStorage Data
  • 5. Re-render Optimization
  • 5.1 Calculate Derived State During Rendering
  • 5.2 Defer State Reads to Usage Point
  • 5.3 Do not wrap a simple expression with a primitive result type in useMemo
  • 5.4 Don't Define Components Inside Components
  • 5.5 Extract Default Non-primitive Parameter Value from Memoized Component to Constant
  • 5.6 Extract to Memoized Components
  • 5.7 Narrow Effect Dependencies
  • 5.8 Put Interaction Logic in Event Handlers
  • 5.9 Subscribe to Derived State
  • 5.10 Use Functional setState Updates
  • 5.11 Use Lazy State Initialization
  • 5.12 Use Transitions for Non-Urgent Updates
  • 5.13 Use useRef for Transient Values
  • 6. Rendering Performance
  • 6.1 Animate SVG Wrapper Instead of SVG Element
  • 6.2 CSS content-visibility for Long Lists
  • 6.3 Hoist Static JSX Elements
  • 6.4 Optimize SVG Precision
  • 6.5 Prevent Hydration Mismatch Without Flickering
  • 6.6 Suppress Expected Hydration Mismatches
  • 6.7 Use Activity Component for Show/Hide
  • 6.8 Use defer or async on Script Tags
  • 6.9 Use Explicit Conditional Rendering
  • 6.10 Use React DOM Resource Hints
  • 6.11 Use useTransition Over Manual Loading States
  • 7. JavaScript Performance
  • 7.1 Avoid Layout Thrashing
  • 7.2 Build Index Maps for Repeated Lookups
  • 7.3 Cache Property Access in Loops
  • 7.4 Cache Repeated Function Calls

What it covers

buildlint-formatcode-stylearchitecturetypessecuritydependenciesapiuiperformancedeploymentdo-not

Stack — with the evidence

typescript

(1.00)

node

(1.00)

vitest

(1.00)

playwright

(1.00)

eslint

(1.00)

biome

(1.00)

react

(0.70)

drizzle

(0.70)

tailwind

(0.70)

vite

(0.70)

aws

(0.70)

desktop-app

(0.70)

javascript

(0.60)

monorepo

(0.60)

pnpm

(0.60)

github-actions

(0.60)

Format

AGENTS.md

A plain-markdown README for coding agents, deliberately unopinionated: no frontmatter, no globs, no vendor keys. That minimalism is why it became the one file a dozen different agents will read, and why it carries the least per-file targeting power of any format here.

What the corpus says about it

Repository

Owner
CherryHQ
Language
—
License
—
Archived
no

All configs in this repo

Also in CherryHQ/cherry-studio

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
CherryHQ/cherry-studioCLAUDE.md · 49kCLAUDE.mdtypescriptnode+14setuptestlint-formatstyle+785/1003 days ago
CherryHQ/cherry-studiopackages/provider-registry/CLAUDE.md · 49kCLAUDE.mdtypescriptnode+14testgitdo-notagent-behaviour80/1003 days ago
Diff against CLAUDE.md Diff against packages/provider-registry/CLAUDE.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199kAGENTS.mdtypescriptlangchain+16buildteststylearch+3100/1003 days ago
TryGhost/Ghoste2e/AGENTS.md · 55kAGENTS.mdtypescriptjavascript+12setupteststylearch+2100/1003 days ago
SkeneTechnologies/skene-cookbookAGENTS.md · 51AGENTS.mdpythoneslint+4setupbuildtestlint-format+7100/1002 days ago
mui/material-uiAGENTS.md · 99kAGENTS.mdtypescriptjavascript+13setupbuildtestlint-format+9100/1003 days ago
aaif-goose/gooseAGENTS.md · 52kAGENTS.mdrusttypescript+2setupbuildtestlint-format+6100/1003 days ago
duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70AGENTS.mdtypescriptjavascript+5buildteststylearch+3100/1003 days ago
trick77/agents-md-syncAGENTS.md · 2AGENTS.mdtypescriptnode+4setupbuildteststyle+5100/1003 days ago
code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67kAGENTS.mdtypescriptbun+10setupbuildtestlint-format+6100/1002 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack