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

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

Quality

65/100

Scores the file, not the repository.

Length

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

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.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.cursor/skills/web-frontend/references/react-best-practices/AGENTS.md · 0AGENTS.mdtypescriptturborepo+18buildstyletypessecurity+665/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 .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 .cursor/skills/web-frontend/references/react-best-practices/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