Ghost

struxadotcloud/struxa

public
Fork0
CodeIssues0Pull requests0
1branch
struxa.claudeskillsvercel-react-best-practicesAGENTS.md
initial commitd4cb9e15 months ago3723 lines · 105.9 KB
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
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
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
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
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
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
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
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
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
115
116This is a specialization of [Defer Await Until Needed](./async-defer-await.md
117
118**Incorrect:**
119
120```typescript
121const someFlag = await
122
123if (someFlag && someCondition
124 // ...
125}
126```
127
128**Correct:**
129
130```typescript
131if (someCondition) {
132 const someFlag =
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
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(
153 const userData =
154
155 if (skipProcessing) {
156 // Returns immediately but still waited for userData
157 return { skipped:
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(
169 if (skipProcessing) {
170 // Returns immediately without waiting
171 return { skipped:
172 }
173
174 // Fetch only when needed
175 const userData =
176 return processUserData(userData
177}
178```
179
180**Another example: early return optimization**
181
182```typescript
183// Incorrect: always fetches permissions
184async function updateResource(
185 const permissions =
186 const resource =
187
188 if (!resource)
189 return { error:
190 }
191
192 if (!permissions.canEdit
193 return { error:
194 }
195
196 return await updateResourceData
197}
198
199// Correct: fetches only when needed
200async function updateResource(
201 const resource =
202
203 if (!resource)
204 return { error:
205 }
206
207 const permissions =
208
209 if (!permissions.canEdit
210 return { error:
211 }
212
213 return await updateResourceData
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
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
231const profile = await
232```
233
234**Correct: config and profile run in parallel**
235
236```typescript
237import { all }
238
239const { user,
240 async user() {
241 return fetchUser();
242 },
243 async config() {
244 return fetchConfig();
245 },
246 async profile() {
247 return fetchProfile((await
248 },
249});
250```
251
252**Alternative without extra dependencies:**
253
254```typescript
255const userPromise = fetchUser
256const profilePromise = userPromise
257
258const [user, config
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
275 const session =
276 const config =
277 const data =
278 return Response.json
279}
280```
281
282**Correct: auth and config start immediately**
283
284```typescript
285export async function GET
286 const sessionPromise =
287 const configPromise =
288 const session =
289 const [config,
290 return Response.json
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
306const posts = await
307const comments = await
308```
309
310**Correct: parallel execution, 1 round trip**
311
312```typescript
313const [user, posts
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 =
327
328 return (
329 <div>
330 <div>Sidebar</div
331 <div>Header</div
332 <div>
333 <DataDisplay 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=
353 <DataDisplay />
354 </Suspense>
355 </div>
356 <div>Footer</div
357 </div>
358 );
359}
360
361async function DataDisplay()
362 const data =
363 return <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 =
375
376 return (
377 <div>
378 <div>Sidebar</div
379 <div>Header</div
380 <Suspense fallback=
381 <DataDisplay dataPromise
382 <DataSummary dataPromise
383 </Suspense>
384 <div>Footer</div
385 </div>
386 );
387}
388
389function DataDisplay({ dataPromise
390 const data =
391 return <div>{
392}
393
394function DataSummary({ dataPromise
395 const data =
396 return <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.,
427
428Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages,
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
436// Loads 1,583 modules, takes ~2.8s extra in dev
437// Runtime cost: 200-800ms on every cold start
438
439import { Button, TextField
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
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:
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`, `
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
484}) {
485 const [frames,
486
487 useEffect(() =>
488 if (enabled &&
489 import("./animation-frames.js")
490 .then((mod)
491 .catch(() =>
492 }
493 }, [enabled,
494
495 if (!frames)
496 return <Canvas
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 }
512
513export default function RootLayout
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
531 ssr: false,
532});
533
534export default function RootLayout
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 }
556
557function CodePanel({ code
558 return <MonacoEditor
559}
560```
561
562**Correct: Monaco loads on demand**
563
564```tsx
565import dynamic from "next/dynamic
566
567const MonacoEditor = dynamic
568 ssr: false,
569});
570
571function CodePanel({ code
572 return <MonacoEditor
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
603```
604
605**Correct: use an explicit map of allowed modules**
606
607```ts
608const PAGE_MODULES = {
609 home: () =>
610 settings: () =>
611} as const;
612
613const Page = await
614```
615
616**Incorrect: a 2-value enum still hides the final path from static analysis**
617
618```ts
619const baseDir = path
620```
621
622**Correct: make each final path literal at the callsite**
623
624```ts
625const baseDir =
626 kind === ContentKind.
627 ? path.join(
628 : path.join(
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`,
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
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
645 const preload =
646 if (typeof window
647 void import("./monaco-editor");
648 }
649 };
650
651 return (
652 <button onMouseEnter={
653 Open Editor
654 </button>
655 );
656}
657```
658
659**Example: preload when feature flag is enabled**
660
661```tsx
662function FlagsProvider({ children
663 useEffect(() =>
664 if (flags.editorEnabled
665 void import("./monaco-editor").
666 }
667 }, [flags.editorEnabled
668
669 return <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
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
697 // Anyone can call this! No auth check
698 await db.user
699 return { success:
700}
701```
702
703**Correct: authentication inside the action**
704
705```typescript
706"use server";
707
708import { verifySession }
709import { unauthorized }
710
711export async function deleteUser
712 // Always check auth inside the action
713 const session =
714
715 if (!session)
716 throw unauthorized("Must be logged in"
717 }
718
719 // Check authorization too
720 if (session.user
721 throw unauthorized("Cannot delete other users"
722 }
723
724 await db.user
725 return { success:
726}
727```
728
729**With input validation:**
730
731```typescript
732"use server";
733
734import { verifySession }
735import { z }
736
737const updateProfileSchema = z
738 userId: z.string
739 name: z.string
740 email: z.string
741});
742
743export async function updateProfile
744 // Validate input first
745 const validated =
746
747 // Then authenticate
748 const session =
749 if (!session)
750 throw new Error(
751 }
752
753 // Then authorize
754 if (session.user
755 throw new Error(
756 }
757
758 // Finally perform the mutation
759 await db.user
760 where: { id: validated
761 data: {
762 name: validated.name
763 email: validated.email
764 },
765 });
766
767 return { success:
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()`,
778
779**Incorrect: duplicates array**
780
781```tsx
782// RSC: sends 6 strings (2 arrays × 3 items)
783<ClientList usernames={
784```
785
786**Correct: sends 3 strings**
787
788```tsx
789// RSC: send once
790<ClientList usernames={
791
792// Client: transform there
793("use client");
794const sorted = useMemo
795```
796
797**Nested deduplication behavior:**
798
799```tsx
800// string[] - duplicates everything
801usernames={['a',
802
803// object[] - duplicates array structure only
804users={[{id:1},{id
805```
806
807Deduplication works recursively. Impact varies by data type:
808
809- `string[]`, `number[]`,
810
811- `object[]`: **LOW impact
812
813**Operations breaking deduplication: create new references**
814
815- Arrays: `.toSorted()`, `.filter()`,
816
817- Objects: `{...obj}`, `Object.assign()`,
818
819**More examples:**
820
821```tsx
822// ❌ Bad
823<C users={
824<C product={
825
826// ✅ Good
827<C users={
828<C 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
846
847export default async function
848 currentUser = await
849 return <Dashboard
850}
851
852async function Dashboard()
853 return <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
863 const user =
864 return <Dashboard
865}
866
867function Dashboard({ user
868 return <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 }
892
893const cache = new
894 max: 1000,
895 ttl: 5 * 60
896});
897
898export async function getUser
899 const cached =
900 if (cached) return
901
902 const user =
903 cache.set(id
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
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 }
930
931export async function GET
932 // Runs on EVERY request - expensive!
933 const fontData =
934 new URL('./fonts/Inter.ttf
935 ).then(res
936
937 const logoData =
938 new URL('./images/logo.png
939 ).then(res
940
941 return new ImageResponse
942 <div style={{
943 <img src={
944 Hello World
945 </div>,
946 { fonts: [{ name
947 )
948}
949```
950
951**Correct: loads once at module initialization**
952
953```typescript
954// app/api/og/route.tsx
955import { ImageResponse }
956
957// Module-level: runs ONCE when module is first imported
958const fontData = fetch
959 new URL('./fonts/Inter.ttf
960).then(res =>
961
962const logoData = fetch
963 new URL('./images/logo.png
964).then(res =>
965
966export async function GET
967 // Await the already-started promises
968 const [font,
969
970 return new ImageResponse
971 <div style={{
972 <img src={
973 Hello World
974 </div>,
975 { fonts: [{ name
976 )
977}
978```
979
980**Correct: synchronous fs at module level**
981
982```typescript
983// app/api/og/route.tsx
984import { ImageResponse }
985import { readFileSync }
986import { join }
987
988// Synchronous read at module level - blocks only during module init
989const fontData = readFileSync
990 join(process.cwd
991)
992
993const logoData = readFileSync
994 join(process.cwd
995)
996
997export async function GET
998 return new ImageResponse
999 <div style={{
1000 <img src={
1001 Hello World
1002 </div>,
1003 { fonts: [{ name
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
1014 const config =
1015 const template =
1016
1017 return render(template
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
1027const templatePromise = fs
1028
1029export async function processRequest
1030 const [config,
1031
1032 return render(template
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
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 =
1073 return <Profile
1074}
1075
1076("use client");
1077function Profile({ user
1078 return <div>{
1079}
1080```
1081
1082**Correct: serializes only 1 field**
1083
1084```tsx
1085async function Page()
1086 const user =
1087 return <Profile
1088}
1089
1090("use client");
1091function Profile({ name
1092 return <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
1106 const header =
1107 return (
1108 <div>
1109 <div>{header}
1110 <Sidebar />
1111 </div>
1112 );
1113}
1114
1115async function Sidebar()
1116 const items =
1117 return <nav>{
1118}
1119```
1120
1121**Correct: both fetch simultaneously**
1122
1123```tsx
1124async function Header()
1125 const data =
1126 return <div>{
1127}
1128
1129async function Sidebar()
1130 const items =
1131 return <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 =
1149 return <div>{
1150}
1151
1152async function Sidebar()
1153 const items =
1154 return <nav>{
1155}
1156
1157function Layout({ children
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
1185
1186const chatAuthors = await
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
1195 chatIds.map((id
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 }
1211
1212export const getCurrentUser =
1213 const session =
1214 if (!session?.user
1215 return await db
1216 where: { id: session
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
1231 return await db
1232});
1233
1234// Each call creates new object, never hits cache
1235getUser({ uid: 1
1236getUser({ uid: 1
1237```
1238
1239**Correct: cache hit**
1240
1241```typescript
1242const params = {
1243getUser(params); //
1244getUser(params); //
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
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 }
1277
1278export async function POST
1279 // Perform mutation
1280 await updateDatabase(request
1281
1282 // Logging blocks the response
1283 const userAgent =
1284 await logUserAction({
1285
1286 return new Response
1287 status: 200,
1288 headers: { "Content-Type":
1289 });
1290}
1291```
1292
1293**Correct: non-blocking**
1294
1295```tsx
1296import { after }
1297import { headers, cookies
1298import { logUserAction }
1299
1300export async function POST
1301 // Perform mutation
1302 await updateDatabase(request
1303
1304 // Log after response is sent
1305 after(async ()
1306 const userAgent =
1307 const sessionCookie =
1308
1309 logUserAction({ sessionCookie,
1310 });
1311
1312 return new Response
1313 status: 200,
1314 headers: { "Content-Type":
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:
1359 useEffect(() =>
1360 const handler =
1361 if (e.metaKey
1362 callback();
1363 }
1364 };
1365 window.addEventListener("keydown
1366 return () =>
1367 }, [key,
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
1380
1381function useKeyboardShortcut(key:
1382 // Register this callback in the Map
1383 useEffect(() =>
1384 if (!keyCallbacks.has
1385 keyCallbacks.set(key
1386 }
1387 keyCallbacks.get(key
1388
1389 return () =>
1390 const set =
1391 if (set) {
1392 set.delete(callback
1393 if (set.size
1394 keyCallbacks.delete(key
1395 }
1396 }
1397 };
1398 }, [key,
1399
1400 useSWRSubscription("global-keydown",
1401 const handler =
1402 if (e.metaKey
1403 keyCallbacks.get(e
1404 }
1405 };
1406 window.addEventListener("keydown
1407 return () =>
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 =
1434 const handleWheel =
1435
1436 document.addEventListener("touchstart
1437 document.addEventListener("wheel
1438
1439 return () =>
1440 document.removeEventListener("touchstart
1441 document.removeEventListener("wheel
1442 };
1443}, []);
1444```
1445
1446**Correct:**
1447
1448```typescript
1449useEffect(() => {
1450 const handleTouch =
1451 const handleWheel =
1452
1453 document.addEventListener("touchstart
1454 document.addEventListener("wheel
1455
1456 return () =>
1457 document.removeEventListener("touchstart
1458 document.removeEventListener("wheel
1459 };
1460}, []);
1461```
1462
1463**Use passive when:** tracking/analytics, logging, any listener that doesn't call `
1464
1465**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `
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,
1478 useEffect(() =>
1479 fetch("/api/users")
1480 .then((r)
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:
1493}
1494```
1495
1496**For immutable data:**
1497
1498```tsx
1499import { useImmutableSWR }
1500
1501function StaticContent() {
1502 const { data
1503}
1504```
1505
1506**For mutations:**
1507
1508```tsx
1509import { useSWRMutation }
1510
1511function UpdateButton() {
1512 const { trigger
1513 return <button onClick
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"
1530const data = localStorage
1531```
1532
1533**Correct:**
1534
1535```typescript
1536const VERSION = "
1537
1538function saveConfig(config:
1539 try {
1540 localStorage.setItem(`userConfig:
1541 } catch {
1542 // Throws in incognito/private browsing, quota exceeded, or disabled
1543 }
1544}
1545
1546function loadConfig() {
1547 try {
1548 const data =
1549 return data ?
1550 } catch {
1551 return null;
1552 }
1553}
1554
1555// Migration from v1 to v2
1556function migrate() {
1557 try {
1558 const v1 =
1559 if (v1) {
1560 const old =
1561 saveConfig({ theme:
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:
1573 try {
1574 localStorage.setItem(
1575 "prefs:v1",
1576 JSON.stringify({
1577 theme: user.preferences
1578 notifications: user.preferences
1579 }),
1580 );
1581 } catch {}
1582}
1583```
1584
1585**Always wrap in try-catch:** `getItem()
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,
1608 const [lastName,
1609 const [fullName,
1610
1611 useEffect(() =>
1612 setFullName(firstName +
1613 }, [firstName,
1614
1615 return <p>{
1616}
1617```
1618
1619**Correct: derive during render**
1620
1621```tsx
1622function Form() {
1623 const [firstName,
1624 const [lastName,
1625 const fullName =
1626
1627 return <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
1643 const searchParams =
1644
1645 const handleShare =
1646 const ref =
1647 shareChat(chatId, {
1648 };
1649
1650 return <button onClick
1651}
1652```
1653
1654**Correct: reads on demand, no subscription**
1655
1656```tsx
1657function ShareButton({ chatId
1658 const handleShare =
1659 const params =
1660 const ref =
1661 shareChat(chatId, {
1662 };
1663
1664 return <button onClick
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
1680 const isLoading =
1681 return user.isLoading
1682 }, [user.isLoading
1683
1684 if (isLoading) return
1685 // return some markup
1686}
1687```
1688
1689**Correct:**
1690
1691```tsx
1692function Header({ user
1693 const isLoading =
1694
1695 if (isLoading) return
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
1712 // Defined inside to access `theme` - BAD
1713 const Avatar =
1714 <img src={
1715 );
1716
1717 // Defined inside to access `user` - BAD
1718 const Stats =
1719 <div>
1720 <span>{user.
1721 <span>{user.
1722 </div>
1723 );
1724
1725 return (
1726 <div>
1727 <Avatar />
1728 <Stats />
1729 </div>
1730 );
1731}
1732```
1733
1734Every time `UserProfile` renders, `Avatar` and `
1735
1736**Correct: pass props instead**
1737
1738```tsx
1739function Avatar({ src
1740 return <img src
1741}
1742
1743function Stats({ followers
1744 return (
1745 <div>
1746 <span>{followers}
1747 <span>{posts}
1748 </div>
1749 );
1750}
1751
1752function UserProfile({ user
1753 return (
1754 <div>
1755 <Avatar src=
1756 <Stats followers=
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
1781
1782```tsx
1783const UserAvatar = memo
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
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
1814 const avatar =
1815 const id =
1816 return <Avatar
1817 }, [user]);
1818
1819 if (loading) return
1820 return <div>{
1821}
1822```
1823
1824**Correct: skips computation when loading**
1825
1826```tsx
1827const UserAvatar = memo
1828 const id =
1829 return <Avatar
1830});
1831
1832function Profile({ user
1833 if (loading) return
1834 return (
1835 <div>
1836 <UserAvatar user=
1837 </div>
1838 );
1839}
1840```
1841
1842**Note:** If your project has [React Compiler
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
1855}, [user]);
1856```
1857
1858**Correct: re-runs only when id changes**
1859
1860```tsx
1861useEffect(() => {
1862 console.log(user
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 <
1872 enableMobileMode();
1873 }
1874}, [width]);
1875
1876// Correct: runs only on boolean transition
1877const isMobile = width
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,
1896 const theme =
1897
1898 useEffect(() =>
1899 if (submitted) {
1900 post("/api/register");
1901 showToast("Registered",
1902 }
1903 }, [submitted,
1904
1905 return <button onClick
1906}
1907```
1908
1909**Correct: do it in the handler**
1910
1911```tsx
1912function Form() {
1913 const theme =
1914
1915 function handleSubmit() {
1916 post("/api/register");
1917 showToast("Registered",
1918 }
1919
1920 return <button onClick
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
1933
1934```tsx
1935const sortedProducts = useMemo
1936 const filtered =
1937 const sorted =
1938 sortOrder === "asc"
1939 );
1940 return sorted;
1941}, [products, category
1942```
1943
1944**Correct: filtering only recomputes when products or category change**
1945
1946```tsx
1947const filteredProducts = useMemo
1948 () => products.
1949 [products, category]
1950);
1951
1952const sortedProducts = useMemo
1953 () =>
1954 filteredProducts.toSorted((a
1955 sortOrder === "asc"
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 =
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 =
1981}, [pageTitle]);
1982```
1983
1984**Note:** If your project has [React Compiler
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 =
1997 const isMobile =
1998 return <nav className
1999}
2000```
2001
2002**Correct: re-renders only when boolean changes**
2003
2004```tsx
2005function Sidebar() {
2006 const isMobile =
2007 return <nav className
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,
2022
2023 // Callback must depend on items, recreated on every items change
2024 const addItems =
2025 (newItems: Item
2026 setItems([...items,
2027 },
2028 [items],
2029 ); // ❌ items dependency causes recreations
2030
2031 // Risk of stale closure if dependency is forgotten
2032 const removeItem =
2033 setItems(items.filter
2034 }, []); //
2035
2036 return <ItemsEditor
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,
2047
2048 // Stable callback, never recreated
2049 const addItems =
2050 setItems((curr)
2051 }, []); //
2052
2053 // Always uses latest state, no stale closure risk
2054 const removeItem =
2055 setItems((curr)
2056 }, []); //
2057
2058 return <ItemsEditor
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
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
2102 // buildSearchIndex() runs on EVERY render, even after initialization
2103 const [searchIndex,
2104 const [query,
2105
2106 // When query changes, buildSearchIndex runs again unnecessarily
2107 return <SearchResults
2108}
2109
2110function UserProfile() {
2111 // JSON.parse runs on every render
2112 const [settings,
2113
2114 return <SettingsForm
2115}
2116```
2117
2118**Correct: runs only once**
2119
2120```tsx
2121function FilteredList({ items
2122 // buildSearchIndex() runs ONLY on initial render
2123 const [searchIndex,
2124 const [query,
2125
2126 return <SearchResults
2127}
2128
2129function UserProfile() {
2130 // JSON.parse runs only on initial render
2131 const [settings,
2132 const stored =
2133 return stored ?
2134 });
2135
2136 return <SettingsForm
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 (
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,
2155 useEffect(() =>
2156 const handler =
2157 window.addEventListener("scroll
2158 return () =>
2159 }, []);
2160}
2161```
2162
2163**Correct: non-blocking updates**
2164
2165```tsx
2166import { startTransition }
2167
2168function ScrollTracker() {
2169 const [scrollY,
2170 useEffect(() =>
2171 const handler =
2172 startTransition(() =>
2173 };
2174 window.addEventListener("scroll
2175 return () =>
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
2190 const [query,
2191 const filtered =
2192
2193 return (
2194 <>
2195 <input value={
2196 <ResultsList results
2197 </>
2198 );
2199}
2200```
2201
2202**Correct: input stays snappy, results render when ready**
2203
2204```tsx
2205function Search({ items
2206 const [query,
2207 const deferredQuery =
2208 const filtered =
2209 () => items.
2210 [items, deferredQuery]
2211 );
2212 const isStale =
2213
2214 return (
2215 <>
2216 <input value={
2217 <div style={
2218 <ResultsList results
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
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,
2248
2249 useEffect(() =>
2250 const onMove =
2251 window.addEventListener("mousemove
2252 return () =>
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 =
2275 const dotRef =
2276
2277 useEffect(() =>
2278 const onMove =
2279 lastXRef.current =
2280 const node =
2281 if (node) {
2282 node.style.transform
2283 }
2284 };
2285 window.addEventListener("mousemove
2286 return () =>
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="
2326 <circle cx="
2327 </svg>
2328 );
2329}
2330```
2331
2332**Correct: animating wrapper div - hardware accelerated**
2333
2334```tsx
2335function LoadingSpinner() {
2336 return (
2337 <div className="
2338 <svg width="
2339 <circle cx="
2340 </svg>
2341 </div>
2342 );
2343}
2344```
2345
2346This applies to all CSS transforms and transitions (`transform`, `opacity`, `
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
2367 return (
2368 <div className="
2369 {messages.map((
2370 <div key={
2371 <Avatar user=
2372 <div>{msg.
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
2393}
2394
2395function Container() {
2396 return <div>{
2397}
2398```
2399
2400**Correct: reuses same element**
2401
2402```tsx
2403const loadingSkeleton = <
2404
2405function Container() {
2406 return <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
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
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
2448 // localStorage is not available on server - throws error
2449 const theme =
2450
2451 return <div className
2452}
2453```
2454
2455Server-side rendering will fail because `localStorage` is undefined.
2456
2457**Incorrect: visual flickering**
2458
2459```tsx
2460function ThemeWrapper({ children
2461 const [theme,
2462
2463 useEffect(() =>
2464 // Runs after hydration - causes visible flash
2465 const stored =
2466 if (stored) {
2467 setTheme(stored);
2468 }
2469 }, []);
2470
2471 return <div className
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
2481 return (
2482 <>
2483 <div id="
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
2511
2512**Incorrect: known mismatch warnings**
2513
2514```tsx
2515function Timestamp() {
2516 return <span>{
2517}
2518```
2519
2520**Correct: suppress expected mismatch only**
2521
2522```tsx
2523function Timestamp() {
2524 return <span suppressHydrationWarning
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 }
2538
2539function Dropdown({ isOpen
2540 return (
2541 <Activity mode=
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`
2557
2558- **`async`
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="
2570 <script src="
2571 </head>
2572 <body>{/* content
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=
2587 <Script src=
2588 </>
2589 );
2590}
2591```
2592
2593**Note:** In Next.js, prefer the `next/script
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
2602
2603**Incorrect: renders "0" when count is 0**
2604
2605```tsx
2606function Badge({ count
2607 return <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
2618 return <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)`
2632
2633- **`preconnect(href)`
2634
2635- **`preload(href, options)`
2636
2637- **`preloadModule(href)`
2638
2639- **`preinit(href, options)`
2640
2641- **`preinitModule(href)`
2642
2643**Example: preconnect to third-party APIs**
2644
2645```tsx
2646import { preconnect, prefetchDNS
2647
2648export default function App
2649 prefetchDNS("https://analytics.example.com");
2650 preconnect("https://api.example.com");
2651
2652 return <main>{
2653}
2654```
2655
2656**Example: preload critical fonts and styles**
2657
2658```tsx
2659import { preload, preinit
2660
2661export default function RootLayout
2662 // Preload font file
2663 preload("/fonts/inter.woff2",
2664
2665 // Fetch and apply critical stylesheet immediately
2666 preinit("/styles/critical.css",
2667
2668 return (
2669 <html>
2670 <body>{children}
2671 </html>
2672 );
2673}
2674```
2675
2676**Example: preload modules for code-split routes**
2677
2678```tsx
2679import { preloadModule, preinitModule
2680
2681function Navigation() {
2682 const preloadDashboard =
2683 preloadModule("/dashboard.js",
2684 };
2685
2686 return (
2687 <nav>
2688 <a href="
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
2721
2722**Incorrect: manual loading state**
2723
2724```tsx
2725function SearchResults() {
2726 const [query,
2727 const [results,
2728 const [isLoading,
2729
2730 const handleSearch =
2731 setIsLoading(true);
2732 setQuery(value);
2733 const data =
2734 setResults(data);
2735 setIsLoading(false);
2736 };
2737
2738 return (
2739 <>
2740 <input onChange={
2741 {isLoading && <Spinner
2742 <ResultsList results
2743 </>
2744 );
2745}
2746```
2747
2748**Correct: useTransition with built-in pending state**
2749
2750```tsx
2751import { useTransition, useState
2752
2753function SearchResults() {
2754 const [query,
2755 const [results,
2756 const [isPending,
2757
2758 const handleSearch =
2759 setQuery(value);
2760
2761 startTransition(async ()
2762 // Fetch and update results
2763 const data =
2764 setResults(data);
2765 });
2766 };
2767
2768 return (
2769 <>
2770 <input onChange={
2771 {isPending && <Spinner
2772 <ResultsList results
2773 </>
2774 );
2775}
2776```
2777
2778**Benefits:**
2779
2780- **Automatic pending state**: No need to manually manage
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
2803
2804**This is OK: browser batches style changes**
2805
2806```typescript
2807function updateElementStyles(element:
2808 // Each line invalidates style, but browser batches the recalculation
2809 element.style.width
2810 element.style.height
2811 element.style.backgroundColor
2812 element.style.border
2813}
2814```
2815
2816**Incorrect: interleaved reads and writes force reflows**
2817
2818```typescript
2819function layoutThrashing(element:
2820 element.style.width
2821 const width =
2822 element.style.height
2823 const height =
2824}
2825```
2826
2827**Correct: batch writes, then read once**
2828
2829```typescript
2830function updateElementStyles(element:
2831 // Batch all writes together
2832 element.style.width
2833 element.style.height
2834 element.style.backgroundColor
2835 element.style.border
2836
2837 // Read after all writes are done (single reflow)
2838 const { width,
2839}
2840```
2841
2842**Correct: batch reads, then writes**
2843
2844```typescript
2845function updateElementStyles(element:
2846 element.classList.add
2847
2848 const { width,
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
2859 const ref =
2860
2861 useEffect(() =>
2862 if (ref.current
2863 ref.current.style
2864 const width =
2865 ref.current.style
2866 }
2867 }, [isHighlighted]);
2868
2869 return <div ref
2870}
2871
2872// Correct: toggle class
2873function Box({ isHighlighted
2874 return <div className
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
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:
2892 return orders.map
2893 ...order,
2894 user: users.find
2895 }));
2896}
2897```
2898
2899**Correct (O(1) per lookup):**
2900
2901```typescript
2902function processOrders(orders:
2903 const userById =
2904
2905 return orders.map
2906 ...order,
2907 user: userById.get
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
2926 process(obj.config
2927}
2928```
2929
2930**Correct: 1 lookup total**
2931
2932```typescript
2933const value = obj
2934const len = arr
2935for (let 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
2950 return (
2951 <div>
2952 {projects.map(project
2953 // slugify() called 100+ times for same project names
2954 const slug = slugify(
2955
2956 return <ProjectCard key={project.id}
2957 })}
2958 </div>
2959 )
2960}
2961```
2962
2963**Correct: cached results**
2964
2965```typescript
2966// Module-level cache
2967const slugifyCache = new
2968
2969function cachedSlugify(text:
2970 if (slugifyCache.has
2971 return slugifyCache.get
2972 }
2973 const result =
2974 slugifyCache.set(text
2975 return result
2976}
2977
2978function ProjectList({ projects
2979 return (
2980 <div>
2981 {projects.map(project
2982 // Computed only once per unique project name
2983 const slug = cachedSlugify(
2984
2985 return <ProjectCard key={project.id}
2986 })}
2987 </div>
2988 )
2989}
2990```
2991
2992**Simpler pattern for single-value functions:**
2993
2994```typescript
2995let isLoggedInCache: boolean
2996
2997function isLoggedIn(): boolean
2998 if (isLoggedInCache !==
2999 return isLoggedInCache;
3000 }
3001
3002 isLoggedInCache = document.
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
3021
3022**Incorrect: reads storage on every call**
3023
3024```typescript
3025function getTheme() {
3026 return localStorage.getItem
3027}
3028// Called 10 times = 10 storage reads
3029```
3030
3031**Correct: Map cache**
3032
3033```typescript
3034const storageCache = new
3035
3036function getLocalStorage(key:
3037 if (!storageCache.has
3038 storageCache.set(key
3039 }
3040 return storageCache.get
3041}
3042
3043function setLocalStorage(key:
3044 localStorage.setItem(key
3045 storageCache.set(key
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
3055
3056function getCookie(name:
3057 if (!cookieCache)
3058 cookieCache = Object.
3059 }
3060 return cookieCache[name
3061}
3062```
3063
3064**Important: invalidate on external changes**
3065
3066```typescript
3067window.addEventListener("storage"
3068 if (e.key
3069});
3070
3071document.addEventListener("visibilitychange"
3072 if (document.visibilityState
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
3090const testers = users
3091const inactive = users
3092```
3093
3094**Correct: 1 iteration**
3095
3096```typescript
3097const admins: User
3098const testers: User
3099const inactive: User
3100
3101for (const user
3102 if (user.isAdmin
3103 if (user.isTester
3104 if (!user.isActive
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:
3118 const results =
3119 setResults(results);
3120
3121 // These block the main thread immediately
3122 analytics.track("search
3123 saveToRecentSearches(query);
3124 prefetchTopResults(results.slice
3125}
3126```
3127
3128**Correct: defers non-critical work to idle time**
3129
3130```typescript
3131function handleSearch(query:
3132 const results =
3133 setResults(results);
3134
3135 // Defer non-critical work to idle periods
3136 requestIdleCallback(() =>
3137 analytics.track("search
3138 });
3139
3140 requestIdleCallback(() =>
3141 saveToRecentSearches(query);
3142 });
3143
3144 requestIdleCallback(() =>
3145 prefetchTopResults(results.slice
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
3155 timeout: 2000,
3156});
3157```
3158
3159**Chunking large tasks:**
3160
3161```typescript
3162function processLargeDataset(items:
3163 let index =
3164
3165 function processChunk(deadline
3166 // Process items while we have idle time (aim for <50ms chunks)
3167 while (index <
3168 processItem(items[index
3169 index++;
3170 }
3171
3172 // Schedule next chunk if more items remain
3173 if (index <
3174 requestIdleCallback(processChunk);
3175 }
3176 }
3177
3178 requestIdleCallback(processChunk);
3179}
3180```
3181
3182**With fallback for unsupported browsers:**
3183
3184```typescript
3185const scheduleIdleWork = window
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:
3224 // Always sorts and joins, even when lengths differ
3225 return current.sort
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:
3235 // Early return if lengths differ
3236 if (current.length
3237 return true;
3238 }
3239 // Only sort when lengths match
3240 const currentSorted =
3241 const originalSorted =
3242 for (let i
3243 if (currentSorted[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:
3271 let hasError =
3272 let errorMessage =
3273
3274 for (const user
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 ?
3287}
3288```
3289
3290**Correct: returns immediately on first error**
3291
3292```typescript
3293function validateUsers(users:
3294 for (const user
3295 if (!user.email
3296 return { valid:
3297 }
3298 if (!user.name
3299 return { valid:
3300 }
3301 }
3302
3303 return { valid:
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
3317 const regex =
3318 const parts =
3319 return <>{parts.
3320}
3321```
3322
3323**Correct: memoize or hoist**
3324
3325```tsx
3326const EMAIL_REGEX = /
3327
3328function Highlighter({ text
3329 const regex =
3330 () => new
3331 [query]
3332 )
3333 const parts =
3334 return <>{parts.
3335}
3336```
3337
3338**Warning: global regex has mutable state**
3339
3340```typescript
3341const regex = /
3342regex.test("foo"
3343regex.test("foo"
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
3358```
3359
3360**Correct: 1 iteration, no intermediate array**
3361
3362```typescript
3363const userNames = users
3364```
3365
3366**More examples:**
3367
3368```typescript
3369// Extract valid emails from responses
3370// Before
3371const emails = responses
3372
3373// After
3374const emails = responses
3375
3376// Parse and filter valid numbers
3377// Before
3378const numbers = strings
3379
3380// After
3381const numbers = strings
3382 const n =
3383 return isNaN(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:
3411 const sorted =
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:
3422 const sorted =
3423 return { oldest:
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:
3433 if (projects.length
3434
3435 let latest =
3436
3437 for (let i
3438 if (projects[i
3439 latest = projects[
3440 }
3441 }
3442
3443 return latest;
3444}
3445
3446function getOldestAndNewest(projects:
3447 if (projects.length
3448
3449 let oldest =
3450 let newest =
3451
3452 for (let i
3453 if (projects[i
3454 if (projects[i
3455 }
3456
3457 return { oldest,
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 = [
3467const min = Math
3468const max = Math
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/
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 = [
3483items.filter(item
3484```
3485
3486**Correct (O(1) per check):**
3487
3488```typescript
3489const allowedIds = new
3490items.filter(item
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
3503 // Mutates the users prop array!
3504 const sorted =
3505 () => users.
3506 [users]
3507 )
3508 return <div
3509}
3510```
3511
3512**Correct: creates new array**
3513
3514```typescript
3515function UserList({ users
3516 // Creates new sorted array, original unchanged
3517 const sorted =
3518 () => users.
3519 [users]
3520 )
3521 return <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 = [
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
3568
3569function ChatRoom({ roomId
3570 const handleConnected =
3571
3572 useEffect(() =>
3573 const connection =
3574 connection.on("connected
3575 connection.connect();
3576
3577 return () =>
3578 }, [roomId,
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
3588
3589function ChatRoom({ roomId
3590 const handleConnected =
3591
3592 useEffect(() =>
3593 const connection =
3594 connection.on("connected
3595 connection.connect();
3596
3597 return () =>
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:
3652 useEffect(() =>
3653 window.addEventListener(event
3654 return () =>
3655 }, [event,
3656}
3657```
3658
3659**Correct: stable subscription**
3660
3661```tsx
3662import { useEffectEvent }
3663
3664function useWindowEvent(event:
3665 const onEvent =
3666
3667 useEffect(() =>
3668 window.addEventListener(event
3669 return () =>
3670 }, [event]);
3671}
3672```
3673
3674**Alternative: use `useEffectEvent
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
3688 const [query,
3689
3690 useEffect(() =>
3691 const timeout =
3692 return () =>
3693 }, [query,
3694}
3695```
3696
3697**Correct: using React's useEffectEvent**
3698
3699```tsx
3700import { useEffectEvent }
3701
3702function SearchInput({ onSearch
3703 const [query,
3704 const onSearchEvent =
3705
3706 useEffect(() =>
3707 const timeout =
3708 return () =>
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
**
)
—
**
CRITICAL
**
)
)
)
)
)
)
)
—
**
CRITICAL
**
)
)
)
)
)
)
)
—
**
HIGH
**
)
)
)
)
)
)
)
)
)
)
)
—
**
MEDIUM-HIGH
**
)
)
)
)
)
—
**
MEDIUM
**
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
—
**
MEDIUM
**
)
)
)
)
)
)
)
)
)
)
)
)
—
**
LOW-MEDIUM
**
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
—
**
LOW
**
)
)
)
)
**
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.
)
for
`
flag && cheapCondition
`
style checks.
getFlag
()
;
)
{
await
getFlag
()
;
`
someCondition
`
is false removes that cost on the cold path.
userId
:
string
,
skipProcessing
:
boolean
)
{
await
fetchUserData
(
userId
)
;
true
}
;
)
;
userId
:
string
,
skipProcessing
:
boolean
)
{
true
}
;
await
fetchUserData
(
userId
)
;
)
;
resourceId
:
string
,
userId
:
string
)
{
await
fetchPermissions
(
userId
)
;
await
getResource
(
resourceId
)
;
{
"
Not found
"
}
;
)
{
"
Forbidden
"
}
;
(
resource
,
permissions
)
;
resourceId
:
string
,
userId
:
string
)
{
await
getResource
(
resourceId
)
;
{
"
Not found
"
}
;
await
fetchPermissions
(
userId
)
;
)
{
"
Forbidden
"
}
;
(
resource
,
permissions
)
;
[
Check Cheap Conditions Before Async Flags
]
(
./async-cheap-condition-before-await.md
)
.
]
=
await
Promise
.
all
([
fetchUser
()
,
fetchConfig
()])
;
fetchProfile
(
user
.
id
)
;
from
"
better-all
"
;
config
,
profile
}
=
await
all
(
{
this
.
$
.
user
)
.
id
)
;
()
;
.
then
(
(
user
)
=>
fetchProfile
(
user
.
id
))
;
,
profile
]
=
await
Promise
.
all
([
userPromise
,
fetchConfig
()
,
profilePromise
])
;
)
(
request
:
Request
)
{
await
auth
()
;
await
fetchConfig
()
;
await
fetchData
(
session
.
user
.
id
)
;
(
{
data
,
config
}
)
;
(
request
:
Request
)
{
auth
()
;
fetchConfig
()
;
await
sessionPromise
;
data
]
=
await
Promise
.
all
([
configPromise
,
fetchData
(
session
.
user
.
id
)])
;
(
{
data
,
config
}
)
;
fetchUser
()
;
fetchPosts
()
;
fetchComments
()
;
,
comments
]
=
await
Promise
.
all
([
fetchUser
()
,
fetchPosts
()
,
fetchComments
()])
;
{
await
fetchData
()
;
//
Blocks entire page
>
>
{
data
}
/>
>
>
>
{
<
Skeleton
/>
}
>
>
{
await
fetchData
()
;
//
Only blocks this component
data
.
content
}
</
div
>
;
fetchData
()
;
>
>
{
<
Skeleton
/>
}
>
=
{
dataPromise
}
/>
=
{
dataPromise
}
/>
>
}
:
{
dataPromise
:
Promise
<
Data
>
}
)
{
use
(
dataPromise
)
;
//
Unwraps the promise
data
.
content
}
</
div
>
;
}
:
{
dataPromise
:
Promise
<
Data
>
}
)
{
use
(
dataPromise
)
;
//
Reuses the same promise
data
.
summary
}
</
div
>
;
`
index.js
`
that does
`
export * from './module'
`
).
**
it takes 200-800ms just to import them
**
, affecting both development speed and production cold starts.
,
Menu
}
from
"
lucide-react
"
;
}
from
"
@mui/material
"
;
,
Menu
}
from
"
lucide-react
"
;
"
;
"
;
**
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.
@mui/icons-material
`
,
`
@tabler/icons-react
`
,
`
react-icons
`
,
`
@headlessui/react
`
,
`
@radix-ui/react-*
`
,
`
lodash
`
,
`
ramda
`
,
`
date-fns
`
,
`
rxjs
`
,
`
react-use
`
.
)
;
.
Dispatch
<
React
.
SetStateAction
<
boolean
>>
;
setFrames
]
=
useState
<
Frame
[]
|
null
>
(
null
)
;
{
!
frames
&&
typeof
window
!==
"
undefined
"
)
{
=>
setFrames
(
mod
.
frames
))
setEnabled
(
false
))
;
frames
,
setEnabled
])
;
return
<
Skeleton
/>
;
frames
=
{
frames
}
/>
;
from
"
@vercel/analytics/react
"
;
(
{
children
}
)
{
"
;
(
()
=>
import
(
"
@vercel/analytics/react
"
)
.
then
(
(
m
)
=>
m
.
Analytics
)
,
{
(
{
children
}
)
{
from
"
./monaco-editor
"
;
}
:
{
code
:
string
}
)
{
value
=
{
code
}
/>
;
"
;
(
()
=>
import
(
"
./monaco-editor
"
)
.
then
(
(
m
)
=>
m
.
MonacoEditor
)
,
{
}
:
{
code
:
string
}
)
{
value
=
{
code
}
/>
;
import
(
PAGE_MODULES
[
pageName
])
;
import
(
"
./pages/home
"
)
,
import
(
"
./pages/settings
"
)
,
PAGE_MODULES
[
pageName
]()
;
.
join
(
process
.
cwd
()
,
"
content/
"
+
contentKind
)
;
Blog
process
.
cwd
()
,
"
content/blog
"
)
process
.
cwd
()
,
"
content/docs
"
)
;
`
require
`
, and
`
fs
`
usage.
)
,
[
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/
)
}
:
{
onClick
:
()
=>
void
}
)
{
()
=>
{
!==
"
undefined
"
)
{
preload
}
onFocus
=
{
preload
}
onClick
=
{
onClick
}
>
,
flags
}
:
Props
)
{
{
&&
typeof
window
!==
"
undefined
"
)
{
then
(
(
mod
)
=>
mod
.
init
())
;
])
;
value
=
{
flags
}
>
{
children
}
</
FlagsContext.Provider
>
;
**
each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.
(
userId
:
string
)
{
.
delete
(
{
where
:
{
id
:
userId
}
}
)
;
true
}
;
from
"
@/lib/auth
"
;
from
"
@/lib/errors
"
;
(
userId
:
string
)
{
await
verifySession
()
;
{
)
;
.
role
!==
"
admin
"
&&
session
.
user
.
id
!==
userId
)
{
)
;
.
delete
(
{
where
:
{
id
:
userId
}
}
)
;
true
}
;
from
"
@/lib/auth
"
;
from
"
zod
"
;
.
object
(
{
()
.
uuid
()
,
()
.
min
(
1
)
.
max
(
100
)
,
()
.
email
()
,
(
data
:
unknown
)
{
updateProfileSchema
.
parse
(
data
)
;
await
verifySession
()
;
{
"
Unauthorized
"
)
;
.
id
!==
validated
.
userId
)
{
"
Can only update own profile
"
)
;
.
update
(
{
.
userId
}
,
,
,
true
}
;
)
`
.map()
`
) in client, not server.
usernames
}
usernamesOrdered
=
{
usernames
.
toSorted
()
}
/>
usernames
}
/>
;
(
()
=>
[
...
usernames
]
.
sort
()
,
[
usernames
])
;
'
b
'
]
} sorted={usernames.toSorted()}
//
sends 4 strings
:
2
}
]
} sorted={users.toSorted()}
//
sends 2 arrays + 2 unique objects (not 4)
`
boolean[]
`
:
**
HIGH impact
**
- array + all primitives fully duplicated
**
- array duplicated, but nested objects deduplicated by reference
`
.map()
`
,
`
.slice()
`
,
`
[...arr]
`
`
structuredClone()
`
,
`
JSON.parse(JSON.stringify())
`
users
}
active
=
{
users
.
filter
(
u
=>
u
.
active
)
}
/>
product
}
productName
=
{
product
.
name
}
/>
users
}
/>
product
}
/>
|
null
=
null
;
Page
()
{
auth
()
;
/>
;
{
currentUser
?.
name
}
</
div
>
;
Page
()
{
await
auth
()
;
user
=
{
user
}
/>
;
}
:
{
user
:
User
|
null
}
)
{
user
?.
name
}
</
div
>
;
)
.
from
"
lru-cache
"
;
LRUCache
<
string
,
any
>
(
{
*
1000
,
//
5 minutes
(
id
:
string
)
{
cache
.
get
(
id
)
;
cached
;
await
db
.
user
.
findUnique
(
{
where
:
{
id
}
}
)
;
,
user
)
;
]
(
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.
)
from
'
next/og
'
(
request
:
Request
)
{
await
fetch
(
'
,
import
.
meta
.
url
)
=>
res
.
arrayBuffer
())
await
fetch
(
'
,
import
.
meta
.
url
)
=>
res
.
arrayBuffer
())
(
fontFamily
:
'
Inter
'
}}
>
logoData
}
/
>
:
'
Inter
'
,
data
:
fontData
}
]
}
from
'
next/og
'
(
'
,
import
.
meta
.
url
)
res
.
arrayBuffer
())
(
'
,
import
.
meta
.
url
)
res
.
arrayBuffer
())
(
request
:
Request
)
{
logo
]
=
await
Promise
.
all
([
fontData
,
logoData
])
(
fontFamily
:
'
Inter
'
}}
>
logo
}
/
>
:
'
Inter
'
,
data
:
font
}
]
}
from
'
next/og
'
from
'
fs
'
from
'
path
'
(
()
,
'
public/fonts/Inter.ttf
'
)
(
()
,
'
public/images/logo.png
'
)
(
request
:
Request
)
{
(
fontFamily
:
'
Inter
'
}}
>
logoData
}
/
>
:
'
Inter
'
,
data
:
fontData
}
]
}
"
;
(
data
:
Data
)
{
JSON
.
parse
(
await
fs
.
readFile
(
"
./config.json
"
,
"
utf-8
"
))
;
await
fs
.
readFile
(
"
./template.html
"
,
"
utf-8
"
)
;
,
data
,
config
)
;
"
;
.
readFile
(
"
./config.json
"
,
"
utf-8
"
)
.
then
(
JSON
.
parse
)
;
.
readFile
(
"
./template.html
"
,
"
utf-8
"
)
;
(
data
:
Data
)
{
template
]
=
await
Promise
.
all
([
configPromise
,
templatePromise
])
;
,
data
,
config
)
;
)
, 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.
{
await
fetchUser
()
;
//
50 fields
user
=
{
user
}
/>
;
}
:
{
user
:
User
}
)
{
user
.
name
}
</
div
>
;
//
uses 1 field
{
await
fetchUser
()
;
name
=
{
user
.
name
}
/>
;
}
:
{
name
:
string
}
)
{
name
}
</
div
>
;
Page
()
{
await
fetchHeader
()
;
</
div
>
{
await
fetchSidebarItems
()
;
items
.
map
(
renderItem
)
}
</
nav
>
;
{
await
fetchHeader
()
;
data
}
</
div
>
;
{
await
fetchSidebarItems
()
;
items
.
map
(
renderItem
)
}
</
nav
>
;
()
{
{
await
fetchHeader
()
;
data
}
</
div
>
;
{
await
fetchSidebarItems
()
;
items
.
map
(
renderItem
)
}
</
nav
>
;
}
:
{
children
:
ReactNode
}
)
{
()
{
Promise
.
all
(
chatIds
.
map
(
(
id
)
=>
getChat
(
id
)))
;
Promise
.
all
(
chats
.
map
(
(
chat
)
=>
getUser
(
chat
.
author
)))
;
Promise
.
all
(
)
=>
getChat
(
id
)
.
then
(
(
chat
)
=>
getUser
(
chat
.
author
)))
,
from
"
react
"
;
cache
(
async
()
=>
{
await
auth
()
;
?.
id
)
return
null
;
.
user
.
findUnique
(
{
.
user
.
id
}
,
(
async
(
params
:
{
uid
:
number
}
)
=>
{
.
user
.
findUnique
(
{
where
:
{
id
:
params
.
uid
}
}
)
;
}
)
;
}
)
;
//
Cache miss, runs query again
uid
:
1
}
;
Query runs
Cache hit (same reference)
`
fetch
`
calls. However,
`
React.cache()
`
is still essential for other async tasks:
)
from
"
@/app/utils
"
;
(
request
:
Request
)
{
)
;
request
.
headers
.
get
(
"
user-agent
"
)
||
"
unknown
"
;
userAgent
}
)
;
(
JSON
.
stringify
(
{
status
:
"
success
"
}
)
,
{
"
application/json
"
}
,
from
"
next/server
"
;
}
from
"
next/headers
"
;
from
"
@/app/utils
"
;
(
request
:
Request
)
{
)
;
=>
{
(
await
headers
())
.
get
(
"
user-agent
"
)
||
"
unknown
"
;
(
await
cookies
())
.
get
(
"
session-id
"
)
?.
value
||
"
anonymous
"
;
userAgent
}
)
;
(
JSON
.
stringify
(
{
status
:
"
success
"
}
)
,
{
"
application/json
"
}
,
)
string
,
callback
:
()
=>
void
)
{
{
(
e
:
KeyboardEvent
)
=>
{
&&
e
.
key
===
key
)
{
"
,
handler
)
;
window
.
removeEventListener
(
"
keydown
"
,
handler
)
;
callback
])
;
"
;
Map
<
string
,
Set
<
()
=>
void
>>
()
;
string
,
callback
:
()
=>
void
)
{
{
(
key
))
{
,
new
Set
())
;
)
!
.
add
(
callback
)
;
{
keyCallbacks
.
get
(
key
)
;
)
;
===
0
)
{
)
;
callback
])
;
()
=>
{
(
e
:
KeyboardEvent
)
=>
{
&&
keyCallbacks
.
has
(
e
.
key
))
{
.
key
)
!
.
forEach
(
(
cb
)
=>
cb
())
;
"
,
handler
)
;
window
.
removeEventListener
(
"
keydown
"
,
handler
)
;
()
=>
{
()
=>
{
(
e
:
TouchEvent
)
=>
console
.
log
(
e
.
touches
[
0
]
.
clientX
)
;
(
e
:
WheelEvent
)
=>
console
.
log
(
e
.
deltaY
)
;
"
,
handleTouch
)
;
"
,
handleWheel
)
;
{
"
,
handleTouch
)
;
"
,
handleWheel
)
;
(
e
:
TouchEvent
)
=>
console
.
log
(
e
.
touches
[
0
]
.
clientX
)
;
(
e
:
WheelEvent
)
=>
console
.
log
(
e
.
deltaY
)
;
"
,
handleTouch
,
{
passive
:
true
}
)
;
"
,
handleWheel
,
{
passive
:
true
}
)
;
{
"
,
handleTouch
)
;
"
,
handleWheel
)
;
preventDefault()
`
.
preventDefault()
`
.
setUsers
]
=
useState
([])
;
{
=>
r
.
json
())
"
;
users
}
=
useSWR
(
"
/api/users
"
,
fetcher
)
;
from
"
@/lib/swr
"
;
}
=
useImmutableSWR
(
"
/api/config
"
,
fetcher
)
;
from
"
swr/mutation
"
;
}
=
useSWRMutation
(
"
/api/user
"
,
updateUser
)
;
=
{
()
=>
trigger
()
}
>
Update
</
button
>
;
)
,
JSON
.
stringify
(
fullUserObject
))
;
.
getItem
(
"
userConfig
"
)
;
v2
"
;
{
theme
:
string
;
language
:
string
}
)
{
${
VERSION
}
`
,
JSON
.
stringify
(
config
))
;
localStorage
.
getItem
(
`
userConfig:
${
VERSION
}
`
)
;
JSON
.
parse
(
data
)
:
null
;
localStorage
.
getItem
(
"
userConfig:v1
"
)
;
JSON
.
parse
(
v1
)
;
old
.
darkMode
?
"
dark
"
:
"
light
"
,
language
:
old
.
lang
}
)
;
"
)
;
FullUser
)
{
.
theme
,
.
notifications
,
`
and
`
setItem()
`
throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
setFirstName
]
=
useState
(
"
First
"
)
;
setLastName
]
=
useState
(
"
Last
"
)
;
setFullName
]
=
useState
(
""
)
;
{
"
"
+
lastName
)
;
lastName
])
;
fullName
}
</
p
>
;
setFirstName
]
=
useState
(
"
First
"
)
;
setLastName
]
=
useState
(
"
Last
"
)
;
firstName
+
"
"
+
lastName
;
fullName
}
</
p
>
;
)
}
:
{
chatId
:
string
}
)
{
useSearchParams
()
;
()
=>
{
searchParams
.
get
(
"
ref
"
)
;
ref
}
)
;
=
{
handleShare
}
>
Share
</
button
>
;
}
:
{
chatId
:
string
}
)
{
()
=>
{
new
URLSearchParams
(
window
.
location
.
search
)
;
params
.
get
(
"
ref
"
)
;
ref
}
)
;
=
{
handleShare
}
>
Share
</
button
>
;
,
notifications
}
:
Props
)
{
useMemo
(
()
=>
{
||
notifications
.
isLoading
;
,
notifications
.
isLoading
])
;
<
Skeleton
/>
;
,
notifications
}
:
Props
)
{
user
.
isLoading
||
notifications
.
isLoading
;
<
Skeleton
/>
;
,
theme
}
)
{
()
=>
(
user
.
avatarUrl
}
className
=
{
theme
===
"
dark
"
?
"
avatar-dark
"
:
"
avatar-light
"
}
/>
()
=>
(
followers
}
followers
</
span
>
posts
}
posts
</
span
>
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.
,
theme
}
:
{
src
:
string
;
theme
:
string
}
)
{
=
{
src
}
className
=
{
theme
===
"
dark
"
?
"
avatar-dark
"
:
"
avatar-light
"
}
/>
;
,
posts
}
:
{
followers
:
number
;
posts
:
number
}
)
{
followers
</
span
>
posts
</
span
>
,
theme
}
)
{
{
user
.
avatarUrl
}
theme
=
{
theme
}
/>
{
user
.
followers
}
posts
=
{
user
.
posts
}
/>
`
has different values on every rerender
**
(
function
UserAvatar
(
{
onClick
=
()
=>
{}
}
:
{
onClick
?
:
()
=>
void
}
)
{
=>
{}
;
(
function
UserAvatar
(
{
onClick
=
NOOP
}
:
{
onClick
?
:
()
=>
void
}
)
{
,
loading
}
:
Props
)
{
useMemo
(
()
=>
{
computeAvatarId
(
user
)
;
id
=
{
id
}
/>
;
<
Skeleton
/>
;
avatar
}
</
div
>
;
(
function
UserAvatar
(
{
user
}
:
{
user
:
User
}
)
{
useMemo
(
()
=>
computeAvatarId
(
user
)
,
[
user
])
;
id
=
{
id
}
/>
;
,
loading
}
:
Props
)
{
<
Skeleton
/>
;
{
user
}
/>
]
(
https://react.dev/learn/react-compiler
)
enabled, manual memoization with
`
memo()
`
and
`
useMemo()
`
is not necessary. The compiler automatically optimizes re-renders.
.
id
)
;
.
id
)
;
])
;
768
)
{
<
768
;
setSubmitted
]
=
useState
(
false
)
;
useContext
(
ThemeContext
)
;
{
theme
)
;
theme
])
;
=
{
()
=>
setSubmitted
(
true
)
}
>
Submit
</
button
>
;
useContext
(
ThemeContext
)
;
theme
)
;
=
{
handleSubmit
}
>
Submit
</
button
>
;
)
`
recomputes filtering
**
(
()
=>
{
products
.
filter
(
(
p
)
=>
p
.
category
===
category
)
;
filtered
.
toSorted
(
(
a
,
b
)
=>
?
a
.
price
-
b
.
price
:
b
.
price
-
a
.
price
,
,
sortOrder
])
;
(
filter
(
(
p
)
=>
p
.
category
===
category
)
,
,
(
,
b
)
=>
?
a
.
price
-
b
.
price
:
b
.
price
-
a
.
price
,
,
)
;
`
${
pageTitle
}
| My App
`
;
])
;
)
;
`
${
pageTitle
}
| My App
`
;
]
(
https://react.dev/learn/react-compiler
)
enabled, it automatically optimizes dependency tracking and may handle some of these cases for you.
useWindowWidth
()
;
//
updates continuously
width
<
768
;
=
{
isMobile
?
"
mobile
"
:
"
desktop
"
}
/>
;
useMediaQuery
(
"
(max-width: 767px)
"
)
;
=
{
isMobile
?
"
mobile
"
:
"
desktop
"
}
/>
;
setItems
]
=
useState
(
initialItems
)
;
useCallback
(
[]
)
=>
{
...
newItems
])
;
useCallback
(
(
id
:
string
)
=>
{
(
(
item
)
=>
item
.
id
!==
id
))
;
❌ Missing items dependency - will use stale items!
items
=
{
items
}
onAdd
=
{
addItems
}
onRemove
=
{
removeItem
}
/>
;
setItems
]
=
useState
(
initialItems
)
;
useCallback
(
(
newItems
:
Item
[]
)
=>
{
=>
[
...
curr
,
...
newItems
])
;
✅ No dependencies needed
useCallback
(
(
id
:
string
)
=>
{
=>
curr
.
filter
(
(
item
)
=>
item
.
id
!==
id
))
;
✅ Safe and stable
items
=
{
items
}
onAdd
=
{
addItems
}
onRemove
=
{
removeItem
}
/>
;
]
(
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.
}
:
{
items
:
Item
[]
}
)
{
setSearchIndex
]
=
useState
(
buildSearchIndex
(
items
))
;
setQuery
]
=
useState
(
""
)
;
index
=
{
searchIndex
}
query
=
{
query
}
/>
;
setSettings
]
=
useState
(
JSON
.
parse
(
localStorage
.
getItem
(
"
settings
"
)
||
"
{}
"
))
;
settings
=
{
settings
}
onChange
=
{
setSettings
}
/>
;
}
:
{
items
:
Item
[]
}
)
{
setSearchIndex
]
=
useState
(
()
=>
buildSearchIndex
(
items
))
;
setQuery
]
=
useState
(
""
)
;
index
=
{
searchIndex
}
query
=
{
query
}
/>
;
setSettings
]
=
useState
(
()
=>
{
localStorage
.
getItem
(
"
settings
"
)
;
JSON
.
parse
(
stored
)
:
{}
;
settings
=
{
settings
}
onChange
=
{
setSettings
}
/>
;
`
useState({})
`
), the function form is unnecessary.
setScrollY
]
=
useState
(
0
)
;
{
()
=>
setScrollY
(
window
.
scrollY
)
;
"
,
handler
,
{
passive
:
true
}
)
;
window
.
removeEventListener
(
"
scroll
"
,
handler
)
;
from
"
react
"
;
setScrollY
]
=
useState
(
0
)
;
{
()
=>
{
setScrollY
(
window
.
scrollY
))
;
"
,
handler
,
{
passive
:
true
}
)
;
window
.
removeEventListener
(
"
scroll
"
,
handler
)
;
}
:
{
items
:
Item
[]
}
)
{
setQuery
]
=
useState
(
""
)
;
items
.
filter
(
(
item
)
=>
fuzzyMatch
(
item
,
query
))
;
query
}
onChange
=
{
(
e
)
=>
setQuery
(
e
.
target
.
value
)
}
/>
=
{
filtered
}
/>
}
:
{
items
:
Item
[]
}
)
{
setQuery
]
=
useState
(
""
)
;
useDeferredValue
(
query
)
;
useMemo
(
filter
(
(
item
)
=>
fuzzyMatch
(
item
,
deferredQuery
))
,
,
query
!==
deferredQuery
;
query
}
onChange
=
{
(
e
)
=>
setQuery
(
e
.
target
.
value
)
}
/>
{
opacity
:
isStale
?
0.7
:
1
}
}
>
=
{
filtered
}
/>
`
with the deferred value as a dependency, otherwise it still runs on every render.
)
setLastX
]
=
useState
(
0
)
;
{
(
e
:
MouseEvent
)
=>
setLastX
(
e
.
clientX
)
;
"
,
onMove
)
;
window
.
removeEventListener
(
"
mousemove
"
,
onMove
)
;
useRef
(
0
)
;
useRef
<
HTMLDivElement
>
(
null
)
;
{
(
e
:
MouseEvent
)
=>
{
e
.
clientX
;
dotRef
.
current
;
=
`
translateX(
${
e
.
clientX
}
px)
`
;
"
,
onMove
)
;
window
.
removeEventListener
(
"
mousemove
"
,
onMove
)
;
animate-spin
"
width
=
"
24
"
height
=
"
24
"
viewBox
=
"
0 0 24 24
"
>
12
"
cy
=
"
12
"
r
=
"
10
"
stroke
=
"
currentColor
"
/>
animate-spin
"
>
24
"
height
=
"
24
"
viewBox
=
"
0 0 24 24
"
>
12
"
cy
=
"
12
"
r
=
"
10
"
stroke
=
"
currentColor
"
/>
translate
`
,
`
scale
`
,
`
rotate
`
). The wrapper div allows browsers to use GPU acceleration for smoother animations.
}
:
{
messages
:
Message
[]
}
)
{
overflow-y-auto h-screen
"
>
msg
)
=>
(
msg
.
id
}
className
=
"
message-item
"
>
{
msg
.
author
}
/>
content
}
</
div
>
=
"
animate-pulse h-20 bg-gray-200
"
/>
;
loading
&&
<
LoadingSkeleton
/>
}
</
div
>
;
div
className
=
"
animate-pulse h-20 bg-gray-200
"
/>
;
loading
&&
loadingSkeleton
}
</
div
>
;
]
(
https://react.dev/learn/react-compiler
)
enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
icon.svg
}
:
{
children
:
ReactNode
}
)
{
localStorage
.
getItem
(
"
theme
"
)
||
"
light
"
;
=
{
theme
}
>
{
children
}
</
div
>
;
}
:
{
children
:
ReactNode
}
)
{
setTheme
]
=
useState
(
"
light
"
)
;
{
localStorage
.
getItem
(
"
theme
"
)
;
=
{
theme
}
>
{
children
}
</
div
>
;
}
:
{
children
:
ReactNode
}
)
{
theme-wrapper
"
>
{
children
}
</
div
>
`
suppressHydrationWarning
`
to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.
new
Date
()
.
toLocaleString
()
}
</
span
>
;
>
{
new
Date
()
.
toLocaleString
()
}
</
span
>
;
from
"
react
"
;
}
:
Props
)
{
{
isOpen
?
"
visible
"
:
"
hidden
"
}
>
**
: Downloads in parallel, executes after HTML parsing completes, maintains execution order
**
: Downloads in parallel, executes immediately when ready, no guaranteed order
()
{
https://example.com/analytics.js
"
/>
/scripts/utils.js
"
/>
*/
}
</
body
>
"
;
()
{
"
https://example.com/analytics.js
"
strategy
=
"
afterInteractive
"
/>
"
/scripts/utils.js
"
strategy
=
"
beforeInteractive
"
/>
`
component with
`
strategy
`
prop instead of raw script tags:
)
`
0
`
,
`
NaN
`
, or other falsy values that render.
}
:
{
count
:
number
}
)
{
count
&&
<
span
className
=
"
badge
"
>
{
count
}
</
span
>
}
</
div
>
;
}
:
{
count
:
number
}
)
{
count
>
0
?
<
span
className
=
"
badge
"
>
{
count
}
</
span
>
:
null
}
</
div
>
;
**
: Resolve DNS for a domain you expect to connect to
**
: Establish connection (DNS + TCP + TLS) to a server
**
: Fetch a resource (stylesheet, font, script, image) you'll use soon
**
: Fetch an ES module you'll use soon
**
: Fetch and evaluate a stylesheet or script
**
: Fetch and evaluate an ES module
}
from
"
react-dom
"
;
()
{
/*
content
*/
}
</
main
>
;
}
from
"
react-dom
"
;
(
{
children
}
)
{
{
as
:
"
font
"
,
type
:
"
font/woff2
"
,
crossOrigin
:
"
anonymous
"
}
)
;
{
as
:
"
style
"
}
)
;
</
body
>
}
from
"
react-dom
"
;
()
=>
{
{
as
:
"
script
"
}
)
;
/dashboard
"
onMouseEnter
=
{
preloadDashboard
}
>
)
`
isPending
`
state and automatically manages transitions.
setQuery
]
=
useState
(
""
)
;
setResults
]
=
useState
([])
;
setIsLoading
]
=
useState
(
false
)
;
async
(
value
:
string
)
=>
{
await
fetchResults
(
value
)
;
(
e
)
=>
handleSearch
(
e
.
target
.
value
)
}
/>
/>
}
=
{
results
}
/>
}
from
"
react
"
;
setQuery
]
=
useState
(
""
)
;
setResults
]
=
useState
([])
;
startTransition
]
=
useTransition
()
;
(
value
:
string
)
=>
{
//
Update input immediately
=>
{
await
fetchResults
(
value
)
;
(
e
)
=>
handleSearch
(
e
.
target
.
value
)
}
/>
/>
}
=
{
results
}
/>
`
setIsLoading(true/false)
`
)
`
getComputedStyle()
`
) between style changes, the browser is forced to trigger a synchronous reflow.
HTMLElement
)
{
=
"
100px
"
;
=
"
200px
"
;
=
"
blue
"
;
=
"
1px solid black
"
;
HTMLElement
)
{
=
"
100px
"
;
element
.
offsetWidth
;
//
Forces reflow
=
"
200px
"
;
element
.
offsetHeight
;
//
Forces another reflow
HTMLElement
)
{
=
"
100px
"
;
=
"
200px
"
;
=
"
blue
"
;
=
"
1px solid black
"
;
height
}
=
element
.
getBoundingClientRect
()
;
HTMLElement
)
{
(
"
highlighted-box
"
)
;
height
}
=
element
.
getBoundingClientRect
()
;
}
:
{
isHighlighted
:
boolean
}
)
{
useRef
<
HTMLDivElement
>
(
null
)
;
{
&&
isHighlighted
)
{
.
width
=
"
100px
"
;
ref
.
current
.
offsetWidth
;
//
Forces layout
.
height
=
"
200px
"
;
=
{
ref
}
>
Content
</
div
>
;
}
:
{
isHighlighted
:
boolean
}
)
{
=
{
isHighlighted
?
"
highlighted-box
"
:
""
}
>
Content
</
div
>
;
)
and
[
CSS Triggers
]
(
https://csstriggers.com/
)
for more information on layout-forcing operations.
Order
[]
,
users
:
User
[]
)
{
(
(
order
)
=>
(
{
(
(
u
)
=>
u
.
id
===
order
.
userId
)
,
Order
[]
,
users
:
User
[]
)
{
new
Map
(
users
.
map
(
(
u
)
=>
[
u
.
id
,
u
]))
;
(
(
order
)
=>
(
{
(
order
.
userId
)
,
=
0
;
i
<
arr
.
length
;
i
++
)
{
.
settings
.
value
)
;
.
config
.
settings
.
value
;
.
length
;
=
0
;
i
<
len
;
i
++
)
{
}
:
{
projects
:
Project
[]
}
)
{
=
>
{
project
.
name
)
slug
=
{
slug
}
/
>
Map
<
string
,
string
>
()
string
)
:
string
{
(
text
))
{
(
text
)
!
slugify
(
text
)
,
result
)
}
:
{
projects
:
Project
[]
}
)
{
=
>
{
project
.
name
)
slug
=
{
slug
}
/
>
|
null
=
null
;
{
null
)
{
cookie
.
includes
(
"
auth=
"
)
;
)
`
are synchronous and expensive. Cache reads in memory.
(
"
theme
"
)
??
"
light
"
;
Map
<
string
,
string
|
null
>
()
;
string
)
{
(
key
))
{
,
localStorage
.
getItem
(
key
))
;
(
key
)
;
string
,
value
:
string
)
{
,
value
)
;
,
value
)
;
//
keep cache in sync
<
string
,
string
>
|
null
=
null
;
string
)
{
{
fromEntries
(
document
.
cookie
.
split
(
"
;
"
)
.
map
(
(
c
)
=>
c
.
split
(
"
=
"
)))
;
]
;
,
(
e
)
=>
{
)
storageCache
.
delete
(
e
.
key
)
;
,
()
=>
{
===
"
visible
"
)
{
.
filter
(
(
u
)
=>
u
.
isAdmin
)
;
.
filter
(
(
u
)
=>
u
.
isTester
)
;
.
filter
(
(
u
)
=>
!
u
.
isActive
)
;
[]
=
[]
;
[]
=
[]
;
[]
=
[]
;
of
users
)
{
)
admins
.
push
(
user
)
;
)
testers
.
push
(
user
)
;
)
inactive
.
push
(
user
)
;
string
)
{
searchItems
(
query
)
;
"
,
{
query
}
)
;
(
0
,
3
))
;
string
)
{
searchItems
(
query
)
;
{
"
,
{
query
}
)
;
{
{
(
0
,
3
))
;
.
track
(
"
page_view
"
,
{
path
:
location
.
pathname
}
)
,
{
Item
[]
)
{
0
;
:
IdleDeadline
)
{
items
.
length
&&
deadline
.
timeRemaining
()
>
0
)
{
])
;
items
.
length
)
{
.
requestIdleCallback
??
(
(
cb
:
()
=>
void
)
=>
setTimeout
(
cb
,
1
))
;
string
[]
,
original
:
string
[]
)
{
()
.
join
()
!==
original
.
sort
()
.
join
()
;
string
[]
,
original
:
string
[]
)
{
!==
original
.
length
)
{
current
.
toSorted
()
;
original
.
toSorted
()
;
=
0
;
i
<
currentSorted
.
length
;
i
++
)
{
]
!==
originalSorted
[
i
])
{
User
[]
)
{
false
;
""
;
of
users
)
{
)
{
;
)
{
;
{
valid
:
false
,
error
:
errorMessage
}
:
{
valid
:
true
}
;
User
[]
)
{
of
users
)
{
)
{
false
,
error
:
"
Email required
"
}
;
)
{
false
,
error
:
"
Name required
"
}
;
true
}
;
,
query
}
:
Props
)
{
new
RegExp
(
`
(
${
query
}
)
`
,
'
gi
'
)
text
.
split
(
regex
)
map
(
(
part
,
i
)
=>
...
)
}
</>
^
[
^
\s
@
]
+
@
[
^
\s
@
]
+
\.
[
^
\s
@
]
+
$
/
,
query
}
:
Props
)
{
useMemo
(
RegExp
(
`
(
${
escapeRegex
(
query
)
}
)
`
,
'
gi
'
)
,
text
.
split
(
regex
)
map
(
(
part
,
i
)
=>
...
)
}
</>
foo
/
g
;
)
;
//
true, lastIndex = 3
)
;
//
false, lastIndex = 0
.
map
(
(
user
)
=>
(
user
.
isActive
?
user
.
name
:
null
))
.
filter
(
Boolean
)
;
.
flatMap
(
(
user
)
=>
(
user
.
isActive
?
[
user
.
name
]
:
[]))
;
.
map
(
(
r
)
=>
(
r
.
success
?
r
.
data
.
email
:
null
))
.
filter
(
Boolean
)
;
.
flatMap
(
(
r
)
=>
(
r
.
success
?
[
r
.
data
.
email
]
:
[]))
;
.
map
(
(
s
)
=>
parseInt
(
s
,
10
))
.
filter
(
(
n
)
=>
!
isNaN
(
n
))
;
.
flatMap
(
(
s
)
=>
{
parseInt
(
s
,
10
)
;
)
?
[]
:
[
n
]
;
Project
[]
)
{
[
...
projects
]
.
sort
(
(
a
,
b
)
=>
b
.
updatedAt
-
a
.
updatedAt
)
;
;
Project
[]
)
{
[
...
projects
]
.
sort
(
(
a
,
b
)
=>
a
.
updatedAt
-
b
.
updatedAt
)
;
sorted
[
0
]
,
newest
:
sorted
[
sorted
.
length
-
1
]
}
;
Project
[]
)
{
===
0
)
return
null
;
projects
[
0
]
;
=
1
;
i
<
projects
.
length
;
i
++
)
{
]
.
updatedAt
>
latest
.
updatedAt
)
{
i
]
;
Project
[]
)
{
===
0
)
return
{
oldest
:
null
,
newest
:
null
}
;
projects
[
0
]
;
projects
[
0
]
;
=
1
;
i
<
projects
.
length
;
i
++
)
{
]
.
updatedAt
<
oldest
.
updatedAt
)
oldest
=
projects
[
i
]
;
]
.
updatedAt
>
newest
.
updatedAt
)
newest
=
projects
[
i
]
;
newest
}
;
5
,
2
,
8
,
1
,
9
]
;
.
min
(
...
numbers
)
;
.
max
(
...
numbers
)
;
)
. Use the loop approach for reliability.
'
a
'
,
'
b
'
,
'
c
'
,
...
]
=>
allowedIds
.
includes
(
item
.
id
))
Set
([
'
a
'
,
'
b
'
,
'
c
'
,
...
])
=>
allowedIds
.
has
(
item
.
id
))
}
:
{
users
:
User
[]
}
)
{
useMemo
(
sort
(
(
a
,
b
)
=>
a
.
name
.
localeCompare
(
b
.
name
))
,
>
{
sorted.map(renderUser)
}
<
/
div
>
}
:
{
users
:
User
[]
}
)
{
useMemo
(
toSorted
(
(
a
,
b
)
=>
a
.
name
.
localeCompare
(
b
.
name
))
,
>
{
sorted.map(renderUser)
}
<
/
div
>
...
items
]
.
sort
(
(
a
,
b
)
=>
a
.
value
-
b
.
value
)
;
}
from
"
react
"
;
,
onConnected
}
:
{
roomId
:
string
;
onConnected
:
()
=>
void
}
)
{
useEffectEvent
(
onConnected
)
;
{
createConnection
(
roomId
)
;
"
,
handleConnected
)
;
connection
.
disconnect
()
;
handleConnected
])
;
}
from
"
react
"
;
,
onConnected
}
:
{
roomId
:
string
;
onConnected
:
()
=>
void
}
)
{
useEffectEvent
(
onConnected
)
;
{
createConnection
(
roomId
)
;
"
,
handleConnected
)
;
connection
.
disconnect
()
;
)
{
;
{
;
)
string
,
handler
:
(
e
)
=>
void
)
{
{
,
handler
)
;
window
.
removeEventListener
(
event
,
handler
)
;
handler
])
;
from
"
react
"
;
string
,
handler
:
(
e
)
=>
void
)
{
useEffectEvent
(
handler
)
;
{
,
onEvent
)
;
window
.
removeEventListener
(
event
,
onEvent
)
;
`
if you're on latest React:
**
}
:
{
onSearch
:
(
q
:
string
)
=>
void
}
)
{
setQuery
]
=
useState
(
""
)
;
{
setTimeout
(
()
=>
onSearch
(
query
)
,
300
)
;
clearTimeout
(
timeout
)
;
onSearch
])
;
from
"
react
"
;
}
:
{
onSearch
:
(
q
:
string
)
=>
void
}
)
{
setQuery
]
=
useState
(
""
)
;
useEffectEvent
(
onSearch
)
;
{
setTimeout
(
()
=>
onSearchEvent
(
query
)
,
300
)
;
clearTimeout
(
timeout
)
;
)
)
)
)
)
)
)