RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/AGENTS.md/itxSaaad/medlens-plus-app

AGENTS.md

.cursor/skills/web-frontend/references/react-best-practices/AGENTS.md
AGENTS.md

Quality

65/100

Scores the file, not the repository.

Length

13,174 words

82 headings · 174 code blocks

Repository

0

— · pushed 1 days ago

Last changed

3 days ago

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

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 Check Cheap Conditions Before Async Flags
  • 1.2 Defer Await Until Needed
  • 1.3 Dependency-Based Parallelization
  • 1.4 Prevent Waterfall Chains in API Routes
  • 1.5 Promise.all() for Independent Operations
  • 1.6 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 Prefer Statically Analyzable Paths
  • 2.6 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 Avoid Shared Module State for Request Data
  • 3.4 Cross-Request LRU Caching
  • 3.5 Hoist Static I/O to Module Level
  • 3.6 Minimize Serialization at RSC Boundaries
  • 3.7 Parallel Data Fetching with Component Composition
  • 3.8 Parallel Nested Data Fetching
  • 3.9 Per-Request Deduplication with React.cache()
  • 3.10 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 Split Combined Hook Computations
  • 5.10 Subscribe to Derived State
  • 5.11 Use Functional setState Updates
  • 5.12 Use Lazy State Initialization
  • 5.13 Use Transitions for Non-Urgent Updates
  • 5.14 Use useDeferredValue for Expensive Derived Renders
  • 5.15 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

What it covers

buildcode-styletypessecuritydependenciesapiuiperformancedeploymentdo-not

Stack — with the evidence

typescript

(1.00)

turborepo

(1.00)

eslint

(1.00)

node

(0.95)

react

(0.70)

nextjs

(0.70)

fastapi

(0.70)

react-native

(0.70)

expo

(0.70)

postgres

(0.70)

redis

(0.70)

tailwind

(0.70)

vitest

(0.70)

pytest

(0.70)

ruff

(0.70)

javascript

(0.60)

monorepo

(0.60)

pnpm

(0.60)

github-actions

(0.60)

python

(0.50)

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
itxSaaad
Language
—
License
—
Archived
no

All configs in this repo

Also in itxSaaad/medlens-plus-app

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
itxSaaad/medlens-plus-app.cursor/rules/testing.mdc · 0Cursor rulestypescriptturborepo+18testtesting-strategyapimonorepo29/1003 days ago
itxSaaad/medlens-plus-app.claude/skills/web-frontend/references/composition-patterns/AGENTS.md · 0AGENTS.mdtypescriptturborepo+18styleapiuido-not57/1003 days ago
itxSaaad/medlens-plus-app.claude/skills/web-frontend/references/react-best-practices/AGENTS.md · 0AGENTS.mdtypescriptturborepo+18buildstyletypessecurity+665/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/agent-discipline.mdc · 0Cursor rulestypescriptturborepo+18teststyleagent-behaviour53/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/devops-ci.mdc · 0Cursor rulestypescriptturborepo+18securitydo-not23/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/engineering-standards.mdc · 0Cursor rulestypescriptturborepo+18test35/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/fastapi-backend.mdc · 0Cursor rulestypescriptturborepo+18testlint-formatstyle66/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/frontend-quality.mdc · 0Cursor rulestypescriptturborepo+18securityui33/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/github-delivery.mdc · 0Cursor rulestypescriptturborepo+18git16/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/graphify.mdc · 0Cursor rulestypescriptturborepo+18do-not45/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/langgraph-ai-workflows.mdc · 0Cursor rulestypescriptturborepo+18do-not32/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/medical-safety.mdc · 0Cursor rulestypescriptturborepo+18do-not23/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/nextjs-frontend.mdc · 0Cursor rulestypescriptturborepo+18teststylearch80/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/pr-quality-gate.mdc · 0Cursor rulestypescriptturborepo+18testgit43/1003 days ago
itxSaaad/medlens-plus-app.cursor/rules/technical-seo.mdc · 0Cursor rulestypescriptturborepo+18no sections24/1003 days ago
itxSaaad/medlens-plus-app.cursor/skills/web-frontend/references/composition-patterns/AGENTS.md · 0AGENTS.mdtypescriptturborepo+18styleapiuido-not45/1003 days ago
itxSaaad/medlens-plus-app.github/copilot-instructions.md · 0Copilot instructionstypescriptturborepo+18testgitdo-notagent-behaviour+170/1003 days ago
itxSaaad/medlens-plus-appAGENTS.md · 0AGENTS.mdtypescriptturborepo+18gitdo-notagent-behaviour59/1003 days ago
itxSaaad/medlens-plus-appapps/mobile/CLAUDE.md · 0CLAUDE.mdtypescriptreact-native+18stylearchmonorepoagent-behaviour86/1003 days ago
itxSaaad/medlens-plus-appCLAUDE.md · 0CLAUDE.mdtypescriptturborepo+18testgitmonorepodo-not+176/1003 days ago
Diff against .cursor/rules/testing.mdc Diff against .claude/skills/web-frontend/references/composition-patterns/AGENTS.md Diff against .claude/skills/web-frontend/references/react-best-practices/AGENTS.md Diff against .cursor/rules/agent-discipline.mdc Diff against .cursor/rules/devops-ci.mdc Diff against .cursor/rules/engineering-standards.mdc Diff against .cursor/rules/fastapi-backend.mdc Diff against .cursor/rules/frontend-quality.mdc Diff against .cursor/rules/github-delivery.mdc Diff against .cursor/rules/graphify.mdc Diff against .cursor/rules/langgraph-ai-workflows.mdc Diff against .cursor/rules/medical-safety.mdc Diff against .cursor/rules/nextjs-frontend.mdc Diff against .cursor/rules/pr-quality-gate.mdc Diff against .cursor/rules/technical-seo.mdc Diff against .cursor/skills/web-frontend/references/composition-patterns/AGENTS.md Diff against .github/copilot-instructions.md Diff against AGENTS.md Diff against apps/mobile/CLAUDE.md Diff against CLAUDE.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
mui/material-uiAGENTS.md · 99kAGENTS.mdtypescriptjavascript+13setupbuildtestlint-format+9100/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
duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70AGENTS.mdtypescriptjavascript+5buildteststylearch+3100/1003 days ago
n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199kAGENTS.mdtypescriptlangchain+16buildteststylearch+3100/1003 days ago
aaif-goose/gooseAGENTS.md · 52kAGENTS.mdrusttypescript+2setupbuildtestlint-format+6100/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