RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/AGENTS.md/zebbern/claude-code-guide

AGENTS.md

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

Quality

64/100

Scores the file, not the repository.

Length

9,811 words

69 headings · 136 code blocks

Repository

4.5k

— · pushed 0 days ago

Last changed

3 days ago

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

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

node

(0.95)

python

(0.80)

aws

(0.70)

typescript

(0.60)

github-actions

(0.60)

javascript

(0.50)

Format

AGENTS.md

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

What the corpus says about it

Repository

Owner
zebbern
Language
—
License
—
Archived
no

All configs in this repo

Also in zebbern/claude-code-guide

Diff this repo’s formats

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

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
zebbern/claude-code-guideskills/composition-patterns/AGENTS.md · 4.5kAGENTS.mdpythonnode+4styleapiuido-not57/1003 days ago
Diff against skills/composition-patterns/AGENTS.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199kAGENTS.mdtypescriptlangchain+16buildteststylearch+3100/1003 days ago
vllm-project/vllmAGENTS.md · 88kAGENTS.mdpythonpytorch+3setuptestlint-formatstyle+5100/1003 days ago
SkeneTechnologies/skene-cookbookAGENTS.md · 51AGENTS.mdpythoneslint+4setupbuildtestlint-format+7100/1002 days ago
duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70AGENTS.mdtypescriptjavascript+5buildteststylearch+3100/1003 days ago
mui/material-uiAGENTS.md · 99kAGENTS.mdtypescriptjavascript+13setupbuildtestlint-format+9100/1003 days ago
OnlyTerp/prompt-cache-skillsAGENTS.md · 112AGENTS.mdpythongithub-actionssetupbuildtestlint-format+5100/1003 days ago
trick77/agents-md-syncAGENTS.md · 2AGENTS.mdtypescriptnode+4setupbuildteststyle+5100/1003 days ago
TryGhost/Ghoste2e/AGENTS.md · 55kAGENTS.mdtypescriptjavascript+12setupteststylearch+2100/1003 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