···11+# Sections
22+33+This file defines all sections, their ordering, impact levels, and descriptions.
44+The section ID (in parentheses) is the filename prefix used to group rules.
55+66+---
77+88+## 1. Eliminating Waterfalls (async)
99+1010+**Impact:** CRITICAL
1111+**Description:** Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.
1212+1313+## 2. Bundle Size Optimization (bundle)
1414+1515+**Impact:** CRITICAL
1616+**Description:** Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.
1717+1818+## 3. Server-Side Performance (server)
1919+2020+**Impact:** HIGH
2121+**Description:** Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.
2222+2323+## 4. Client-Side Data Fetching (client)
2424+2525+**Impact:** MEDIUM-HIGH
2626+**Description:** Automatic deduplication and efficient data fetching patterns reduce redundant network requests.
2727+2828+## 5. Re-render Optimization (rerender)
2929+3030+**Impact:** MEDIUM
3131+**Description:** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
3232+3333+## 6. Rendering Performance (rendering)
3434+3535+**Impact:** MEDIUM
3636+**Description:** Optimizing the rendering process reduces the work the browser needs to do.
3737+3838+## 7. JavaScript Performance (js)
3939+4040+**Impact:** LOW-MEDIUM
4141+**Description:** Micro-optimizations for hot paths can add up to meaningful improvements.
4242+4343+## 8. Advanced Patterns (advanced)
4444+4545+**Impact:** LOW
4646+**Description:** Advanced patterns for specific cases that require careful implementation.
···11+---
22+title: Rule Title Here
33+impact: MEDIUM
44+impactDescription: Optional description of impact (e.g., "20-50% improvement")
55+tags: tag1, tag2
66+---
77+88+## Rule Title Here
99+1010+**Impact: MEDIUM (optional impact description)**
1111+1212+Brief explanation of the rule and why it matters. This should be clear and concise, explaining the performance implications.
1313+1414+**Incorrect (description of what's wrong):**
1515+1616+```typescript
1717+// Bad code example here
1818+const bad = example()
1919+```
2020+2121+**Correct (description of what's right):**
2222+2323+```typescript
2424+// Good code example here
2525+const good = example()
2626+```
2727+2828+Reference: [Link to documentation or resource](https://example.com)
···11+---
22+title: Initialize App Once, Not Per Mount
33+impact: LOW-MEDIUM
44+impactDescription: avoids duplicate init in development
55+tags: initialization, useEffect, app-startup, side-effects
66+---
77+88+## Initialize App Once, Not Per Mount
99+1010+Do 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.
1111+1212+**Incorrect (runs twice in dev, re-runs on remount):**
1313+1414+```tsx
1515+function Comp() {
1616+ useEffect(() => {
1717+ loadFromStorage()
1818+ checkAuthToken()
1919+ }, [])
2020+2121+ // ...
2222+}
2323+```
2424+2525+**Correct (once per app load):**
2626+2727+```tsx
2828+let didInit = false
2929+3030+function Comp() {
3131+ useEffect(() => {
3232+ if (didInit) return
3333+ didInit = true
3434+ loadFromStorage()
3535+ checkAuthToken()
3636+ }, [])
3737+3838+ // ...
3939+}
4040+```
4141+4242+Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
···11+---
22+title: Avoid Barrel File Imports
33+impact: CRITICAL
44+impactDescription: 200-800ms import cost, slow builds
55+tags: bundle, imports, tree-shaking, barrel-files, performance
66+---
77+88+## Avoid Barrel File Imports
99+1010+Import 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'`).
1111+1212+Popular 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.
1313+1414+**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.
1515+1616+**Incorrect (imports entire library):**
1717+1818+```tsx
1919+import { Check, X, Menu } from 'lucide-react'
2020+// Loads 1,583 modules, takes ~2.8s extra in dev
2121+// Runtime cost: 200-800ms on every cold start
2222+2323+import { Button, TextField } from '@mui/material'
2424+// Loads 2,225 modules, takes ~4.2s extra in dev
2525+```
2626+2727+**Correct (imports only what you need):**
2828+2929+```tsx
3030+import Check from 'lucide-react/dist/esm/icons/check'
3131+import X from 'lucide-react/dist/esm/icons/x'
3232+import Menu from 'lucide-react/dist/esm/icons/menu'
3333+// Loads only 3 modules (~2KB vs ~1MB)
3434+3535+import Button from '@mui/material/Button'
3636+import TextField from '@mui/material/TextField'
3737+// Loads only what you use
3838+```
3939+4040+**Alternative (Next.js 13.5+):**
4141+4242+```js
4343+// next.config.js - use optimizePackageImports
4444+module.exports = {
4545+ experimental: {
4646+ optimizePackageImports: ['lucide-react', '@mui/material']
4747+ }
4848+}
4949+5050+// Then you can keep the ergonomic barrel imports:
5151+import { Check, X, Menu } from 'lucide-react'
5252+// Automatically transformed to direct imports at build time
5353+```
5454+5555+Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
5656+5757+Libraries 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`.
5858+5959+Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
···11+---
22+title: Early Length Check for Array Comparisons
33+impact: MEDIUM-HIGH
44+impactDescription: avoids expensive operations when lengths differ
55+tags: javascript, arrays, performance, optimization, comparison
66+---
77+88+## Early Length Check for Array Comparisons
99+1010+When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
1111+1212+In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
1313+1414+**Incorrect (always runs expensive comparison):**
1515+1616+```typescript
1717+function hasChanges(current: string[], original: string[]) {
1818+ // Always sorts and joins, even when lengths differ
1919+ return current.sort().join() !== original.sort().join()
2020+}
2121+```
2222+2323+Two 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.
2424+2525+**Correct (O(1) length check first):**
2626+2727+```typescript
2828+function hasChanges(current: string[], original: string[]) {
2929+ // Early return if lengths differ
3030+ if (current.length !== original.length) {
3131+ return true
3232+ }
3333+ // Only sort when lengths match
3434+ const currentSorted = current.toSorted()
3535+ const originalSorted = original.toSorted()
3636+ for (let i = 0; i < currentSorted.length; i++) {
3737+ if (currentSorted[i] !== originalSorted[i]) {
3838+ return true
3939+ }
4040+ }
4141+ return false
4242+}
4343+```
4444+4545+This new approach is more efficient because:
4646+- It avoids the overhead of sorting and joining the arrays when lengths differ
4747+- It avoids consuming memory for the joined strings (especially important for large arrays)
4848+- It avoids mutating the original arrays
4949+- It returns early when a difference is found
···11+---
22+title: Use Loop for Min/Max Instead of Sort
33+impact: LOW
44+impactDescription: O(n) instead of O(n log n)
55+tags: javascript, arrays, performance, sorting, algorithms
66+---
77+88+## Use Loop for Min/Max Instead of Sort
99+1010+Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
1111+1212+**Incorrect (O(n log n) - sort to find latest):**
1313+1414+```typescript
1515+interface Project {
1616+ id: string
1717+ name: string
1818+ updatedAt: number
1919+}
2020+2121+function getLatestProject(projects: Project[]) {
2222+ const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
2323+ return sorted[0]
2424+}
2525+```
2626+2727+Sorts the entire array just to find the maximum value.
2828+2929+**Incorrect (O(n log n) - sort for oldest and newest):**
3030+3131+```typescript
3232+function getOldestAndNewest(projects: Project[]) {
3333+ const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
3434+ return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
3535+}
3636+```
3737+3838+Still sorts unnecessarily when only min/max are needed.
3939+4040+**Correct (O(n) - single loop):**
4141+4242+```typescript
4343+function getLatestProject(projects: Project[]) {
4444+ if (projects.length === 0) return null
4545+4646+ let latest = projects[0]
4747+4848+ for (let i = 1; i < projects.length; i++) {
4949+ if (projects[i].updatedAt > latest.updatedAt) {
5050+ latest = projects[i]
5151+ }
5252+ }
5353+5454+ return latest
5555+}
5656+5757+function getOldestAndNewest(projects: Project[]) {
5858+ if (projects.length === 0) return { oldest: null, newest: null }
5959+6060+ let oldest = projects[0]
6161+ let newest = projects[0]
6262+6363+ for (let i = 1; i < projects.length; i++) {
6464+ if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
6565+ if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
6666+ }
6767+6868+ return { oldest, newest }
6969+}
7070+```
7171+7272+Single pass through the array, no copying, no sorting.
7373+7474+**Alternative (Math.min/Math.max for small arrays):**
7575+7676+```typescript
7777+const numbers = [5, 2, 8, 1, 9]
7878+const min = Math.min(...numbers)
7979+const max = Math.max(...numbers)
8080+```
8181+8282+This 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.
···11+---
22+title: Use toSorted() Instead of sort() for Immutability
33+impact: MEDIUM-HIGH
44+impactDescription: prevents mutation bugs in React state
55+tags: javascript, arrays, immutability, react, state, mutation
66+---
77+88+## Use toSorted() Instead of sort() for Immutability
99+1010+`.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.
1111+1212+**Incorrect (mutates original array):**
1313+1414+```typescript
1515+function UserList({ users }: { users: User[] }) {
1616+ // Mutates the users prop array!
1717+ const sorted = useMemo(
1818+ () => users.sort((a, b) => a.name.localeCompare(b.name)),
1919+ [users]
2020+ )
2121+ return <div>{sorted.map(renderUser)}</div>
2222+}
2323+```
2424+2525+**Correct (creates new array):**
2626+2727+```typescript
2828+function UserList({ users }: { users: User[] }) {
2929+ // Creates new sorted array, original unchanged
3030+ const sorted = useMemo(
3131+ () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
3232+ [users]
3333+ )
3434+ return <div>{sorted.map(renderUser)}</div>
3535+}
3636+```
3737+3838+**Why this matters in React:**
3939+4040+1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
4141+2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
4242+4343+**Browser support (fallback for older browsers):**
4444+4545+`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
4646+4747+```typescript
4848+// Fallback for older browsers
4949+const sorted = [...items].sort((a, b) => a.value - b.value)
5050+```
5151+5252+**Other immutable array methods:**
5353+5454+- `.toSorted()` - immutable sort
5555+- `.toReversed()` - immutable reverse
5656+- `.toSpliced()` - immutable splice
5757+- `.with()` - immutable element replacement
···11+---
22+title: Suppress Expected Hydration Mismatches
33+impact: LOW-MEDIUM
44+impactDescription: avoids noisy hydration warnings for known differences
55+tags: rendering, hydration, ssr, nextjs
66+---
77+88+## Suppress Expected Hydration Mismatches
99+1010+In 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.
1111+1212+**Incorrect (known mismatch warnings):**
1313+1414+```tsx
1515+function Timestamp() {
1616+ return <span>{new Date().toLocaleString()}</span>
1717+}
1818+```
1919+2020+**Correct (suppress expected mismatch only):**
2121+2222+```tsx
2323+function Timestamp() {
2424+ return (
2525+ <span suppressHydrationWarning>
2626+ {new Date().toLocaleString()}
2727+ </span>
2828+ )
2929+}
3030+```
···11+---
22+title: Calculate Derived State During Rendering
33+impact: MEDIUM
44+impactDescription: avoids redundant renders and state drift
55+tags: rerender, derived-state, useEffect, state
66+---
77+88+## Calculate Derived State During Rendering
99+1010+If 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.
1111+1212+**Incorrect (redundant state and effect):**
1313+1414+```tsx
1515+function Form() {
1616+ const [firstName, setFirstName] = useState('First')
1717+ const [lastName, setLastName] = useState('Last')
1818+ const [fullName, setFullName] = useState('')
1919+2020+ useEffect(() => {
2121+ setFullName(firstName + ' ' + lastName)
2222+ }, [firstName, lastName])
2323+2424+ return <p>{fullName}</p>
2525+}
2626+```
2727+2828+**Correct (derive during render):**
2929+3030+```tsx
3131+function Form() {
3232+ const [firstName, setFirstName] = useState('First')
3333+ const [lastName, setLastName] = useState('Last')
3434+ const fullName = firstName + ' ' + lastName
3535+3636+ return <p>{fullName}</p>
3737+}
3838+```
3939+4040+References: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
···11+---
22+title: Use Functional setState Updates
33+impact: MEDIUM
44+impactDescription: prevents stale closures and unnecessary callback recreations
55+tags: react, hooks, useState, useCallback, callbacks, closures
66+---
77+88+## Use Functional setState Updates
99+1010+When 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.
1111+1212+**Incorrect (requires state as dependency):**
1313+1414+```tsx
1515+function TodoList() {
1616+ const [items, setItems] = useState(initialItems)
1717+1818+ // Callback must depend on items, recreated on every items change
1919+ const addItems = useCallback((newItems: Item[]) => {
2020+ setItems([...items, ...newItems])
2121+ }, [items]) // ❌ items dependency causes recreations
2222+2323+ // Risk of stale closure if dependency is forgotten
2424+ const removeItem = useCallback((id: string) => {
2525+ setItems(items.filter(item => item.id !== id))
2626+ }, []) // ❌ Missing items dependency - will use stale items!
2727+2828+ return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
2929+}
3030+```
3131+3232+The 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.
3333+3434+**Correct (stable callbacks, no stale closures):**
3535+3636+```tsx
3737+function TodoList() {
3838+ const [items, setItems] = useState(initialItems)
3939+4040+ // Stable callback, never recreated
4141+ const addItems = useCallback((newItems: Item[]) => {
4242+ setItems(curr => [...curr, ...newItems])
4343+ }, []) // ✅ No dependencies needed
4444+4545+ // Always uses latest state, no stale closure risk
4646+ const removeItem = useCallback((id: string) => {
4747+ setItems(curr => curr.filter(item => item.id !== id))
4848+ }, []) // ✅ Safe and stable
4949+5050+ return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
5151+}
5252+```
5353+5454+**Benefits:**
5555+5656+1. **Stable callback references** - Callbacks don't need to be recreated when state changes
5757+2. **No stale closures** - Always operates on the latest state value
5858+3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
5959+4. **Prevents bugs** - Eliminates the most common source of React closure bugs
6060+6161+**When to use functional updates:**
6262+6363+- Any setState that depends on the current state value
6464+- Inside useCallback/useMemo when state is needed
6565+- Event handlers that reference state
6666+- Async operations that update state
6767+6868+**When direct updates are fine:**
6969+7070+- Setting state to a static value: `setCount(0)`
7171+- Setting state from props/arguments only: `setName(newName)`
7272+- State doesn't depend on previous value
7373+7474+**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.
···11+---
22+title: Use Lazy State Initialization
33+impact: MEDIUM
44+impactDescription: wasted computation on every render
55+tags: react, hooks, useState, performance, initialization
66+---
77+88+## Use Lazy State Initialization
99+1010+Pass 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.
1111+1212+**Incorrect (runs on every render):**
1313+1414+```tsx
1515+function FilteredList({ items }: { items: Item[] }) {
1616+ // buildSearchIndex() runs on EVERY render, even after initialization
1717+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
1818+ const [query, setQuery] = useState('')
1919+2020+ // When query changes, buildSearchIndex runs again unnecessarily
2121+ return <SearchResults index={searchIndex} query={query} />
2222+}
2323+2424+function UserProfile() {
2525+ // JSON.parse runs on every render
2626+ const [settings, setSettings] = useState(
2727+ JSON.parse(localStorage.getItem('settings') || '{}')
2828+ )
2929+3030+ return <SettingsForm settings={settings} onChange={setSettings} />
3131+}
3232+```
3333+3434+**Correct (runs only once):**
3535+3636+```tsx
3737+function FilteredList({ items }: { items: Item[] }) {
3838+ // buildSearchIndex() runs ONLY on initial render
3939+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
4040+ const [query, setQuery] = useState('')
4141+4242+ return <SearchResults index={searchIndex} query={query} />
4343+}
4444+4545+function UserProfile() {
4646+ // JSON.parse runs only on initial render
4747+ const [settings, setSettings] = useState(() => {
4848+ const stored = localStorage.getItem('settings')
4949+ return stored ? JSON.parse(stored) : {}
5050+ })
5151+5252+ return <SettingsForm settings={settings} onChange={setSettings} />
5353+}
5454+```
5555+5656+Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
5757+5858+For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
···11+---
22+33+title: Extract Default Non-primitive Parameter Value from Memoized Component to Constant
44+impact: MEDIUM
55+impactDescription: restores memoization by using a constant for default value
66+tags: rerender, memo, optimization
77+88+---
99+1010+## Extract Default Non-primitive Parameter Value from Memoized Component to Constant
1111+1212+When 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()`.
1313+1414+To address this issue, extract the default value into a constant.
1515+1616+**Incorrect (`onClick` has different values on every rerender):**
1717+1818+```tsx
1919+const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
2020+ // ...
2121+})
2222+2323+// Used without optional onClick
2424+<UserAvatar />
2525+```
2626+2727+**Correct (stable default value):**
2828+2929+```tsx
3030+const NOOP = () => {};
3131+3232+const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
3333+ // ...
3434+})
3535+3636+// Used without optional onClick
3737+<UserAvatar />
3838+```
···11+---
22+title: Put Interaction Logic in Event Handlers
33+impact: MEDIUM
44+impactDescription: avoids effect re-runs and duplicate side effects
55+tags: rerender, useEffect, events, side-effects, dependencies
66+---
77+88+## Put Interaction Logic in Event Handlers
99+1010+If 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.
1111+1212+**Incorrect (event modeled as state + effect):**
1313+1414+```tsx
1515+function Form() {
1616+ const [submitted, setSubmitted] = useState(false)
1717+ const theme = useContext(ThemeContext)
1818+1919+ useEffect(() => {
2020+ if (submitted) {
2121+ post('/api/register')
2222+ showToast('Registered', theme)
2323+ }
2424+ }, [submitted, theme])
2525+2626+ return <button onClick={() => setSubmitted(true)}>Submit</button>
2727+}
2828+```
2929+3030+**Correct (do it in the handler):**
3131+3232+```tsx
3333+function Form() {
3434+ const theme = useContext(ThemeContext)
3535+3636+ function handleSubmit() {
3737+ post('/api/register')
3838+ showToast('Registered', theme)
3939+ }
4040+4141+ return <button onClick={handleSubmit}>Submit</button>
4242+}
4343+```
4444+4545+Reference: [Should this code move to an event handler?](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
···11+---
22+title: Do not wrap a simple expression with a primitive result type in useMemo
33+impact: LOW-MEDIUM
44+impactDescription: wasted computation on every render
55+tags: rerender, useMemo, optimization
66+---
77+88+## Do not wrap a simple expression with a primitive result type in useMemo
99+1010+When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.
1111+Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.
1212+1313+**Incorrect:**
1414+1515+```tsx
1616+function Header({ user, notifications }: Props) {
1717+ const isLoading = useMemo(() => {
1818+ return user.isLoading || notifications.isLoading
1919+ }, [user.isLoading, notifications.isLoading])
2020+2121+ if (isLoading) return <Skeleton />
2222+ // return some markup
2323+}
2424+```
2525+2626+**Correct:**
2727+2828+```tsx
2929+function Header({ user, notifications }: Props) {
3030+ const isLoading = user.isLoading || notifications.isLoading
3131+3232+ if (isLoading) return <Skeleton />
3333+ // return some markup
3434+}
3535+```
···11+---
22+title: Cross-Request LRU Caching
33+impact: HIGH
44+impactDescription: caches across requests
55+tags: server, cache, lru, cross-request
66+---
77+88+## Cross-Request LRU Caching
99+1010+`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.
1111+1212+**Implementation:**
1313+1414+```typescript
1515+import { LRUCache } from 'lru-cache'
1616+1717+const cache = new LRUCache<string, any>({
1818+ max: 1000,
1919+ ttl: 5 * 60 * 1000 // 5 minutes
2020+})
2121+2222+export async function getUser(id: string) {
2323+ const cached = cache.get(id)
2424+ if (cached) return cached
2525+2626+ const user = await db.user.findUnique({ where: { id } })
2727+ cache.set(id, user)
2828+ return user
2929+}
3030+3131+// Request 1: DB query, result cached
3232+// Request 2: cache hit, no DB query
3333+```
3434+3535+Use when sequential user actions hit multiple endpoints needing the same data within seconds.
3636+3737+**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.
3838+3939+**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.
4040+4141+Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
···11+---
22+title: Minimize Serialization at RSC Boundaries
33+impact: HIGH
44+impactDescription: reduces data transfer size
55+tags: server, rsc, serialization, props
66+---
77+88+## Minimize Serialization at RSC Boundaries
99+1010+The 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.
1111+1212+**Incorrect (serializes all 50 fields):**
1313+1414+```tsx
1515+async function Page() {
1616+ const user = await fetchUser() // 50 fields
1717+ return <Profile user={user} />
1818+}
1919+2020+'use client'
2121+function Profile({ user }: { user: User }) {
2222+ return <div>{user.name}</div> // uses 1 field
2323+}
2424+```
2525+2626+**Correct (serializes only 1 field):**
2727+2828+```tsx
2929+async function Page() {
3030+ const user = await fetchUser()
3131+ return <Profile name={user.name} />
3232+}
3333+3434+'use client'
3535+function Profile({ name }: { name: string }) {
3636+ return <div>{name}</div>
3737+}
3838+```