RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/AGENTS.md/coco-research/coco

AGENTS.md

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

Quality

64/100

Scores the file, not the repository.

Length

9,864 words

69 headings · 136 code blocks

Repository

196

— · pushed 0 days ago

Last changed

3 days ago

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

Commands it names

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

Sections

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

What it covers

buildlint-formatcode-stylearchitecturetypessecuritydependenciesapiuiperformancedeploymentdo-not

Stack — with the evidence

javascript

(1.00)

node

(0.95)

react

(0.70)

hono

(0.70)

vite

(0.70)

cloudflare

(0.70)

typescript

(0.60)

github-actions

(0.60)

Format

AGENTS.md

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

What the corpus says about it

Repository

Owner
coco-research
Language
—
License
—
Archived
no

All configs in this repo

Similar configs

Same format, overlapping stack, ranked by quality.

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

Built by

Kynth Studio

Directory

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

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

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

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

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

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack