@numbered/docs-to-context 0.1.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +29 -12
  2. package/docs/liquid/README.mdx +59 -0
  3. package/docs/liquid/a11y-alt-text.mdx +16 -0
  4. package/docs/liquid/a11y-semantic-html.mdx +19 -0
  5. package/docs/liquid/alpine-cleanup.mdx +18 -0
  6. package/docs/liquid/alpine-debounce.mdx +12 -0
  7. package/docs/liquid/alpine-defer-heavy.mdx +13 -0
  8. package/docs/liquid/alpine-separate-scopes.mdx +19 -0
  9. package/docs/liquid/alpine-static-data.mdx +16 -0
  10. package/docs/liquid/css-critical-inline.mdx +16 -0
  11. package/docs/liquid/css-prefer-tailwind.mdx +30 -0
  12. package/docs/liquid/liquid-assign-over-capture.mdx +17 -0
  13. package/docs/liquid/liquid-avoid-nested-loops.mdx +21 -0
  14. package/docs/liquid/liquid-break-continue.mdx +23 -0
  15. package/docs/liquid/liquid-cache-assigns.mdx +16 -0
  16. package/docs/liquid/liquid-elsif-chain.mdx +25 -0
  17. package/docs/liquid/liquid-filter-early.mdx +18 -0
  18. package/docs/liquid/liquid-limit-loops.mdx +17 -0
  19. package/docs/liquid/liquid-map-join.mdx +16 -0
  20. package/docs/liquid/liquid-render-over-include.mdx +13 -0
  21. package/docs/liquid/liquid-snippet-data.mdx +17 -0
  22. package/docs/liquid/liquid-whitespace-control.mdx +17 -0
  23. package/docs/liquid/loading-defer-scripts.mdx +13 -0
  24. package/docs/liquid/loading-lazy-images.mdx +31 -0
  25. package/docs/liquid/loading-preload-critical.mdx +24 -0
  26. package/docs/liquid/loading-responsive-images.mdx +23 -0
  27. package/docs/liquid/schema-blocks-over-settings.mdx +35 -0
  28. package/docs/liquid/schema-section-settings.mdx +36 -0
  29. package/docs/react/README.mdx +99 -0
  30. package/docs/react/advanced-handler-refs.mdx +15 -0
  31. package/docs/react/advanced-use-latest.mdx +21 -0
  32. package/docs/react/async-defer-await.mdx +19 -0
  33. package/docs/react/async-promise-all.mdx +17 -0
  34. package/docs/react/async-start-early.mdx +25 -0
  35. package/docs/react/async-suspense-boundaries.mdx +35 -0
  36. package/docs/react/bundle-barrel-imports.mdx +25 -0
  37. package/docs/react/bundle-conditional-loading.mdx +21 -0
  38. package/docs/react/bundle-defer-third-party.mdx +15 -0
  39. package/docs/react/bundle-dynamic-imports.mdx +15 -0
  40. package/docs/react/bundle-preload-intent.mdx +24 -0
  41. package/docs/react/client-event-listeners.mdx +45 -0
  42. package/docs/react/client-swr-dedup.mdx +40 -0
  43. package/docs/react/effect-derive-state.mdx +12 -0
  44. package/docs/react/effect-event-handlers.mdx +16 -0
  45. package/docs/react/effect-key-reset.mdx +11 -0
  46. package/docs/react/effect-no-chains.mdx +22 -0
  47. package/docs/react/effect-notify-parents.mdx +18 -0
  48. package/docs/react/effect-use-memo.mdx +17 -0
  49. package/docs/react/js-batch-css.mdx +23 -0
  50. package/docs/react/js-cache-property.mdx +17 -0
  51. package/docs/react/js-cache-storage.mdx +29 -0
  52. package/docs/react/js-combine-iterations.mdx +20 -0
  53. package/docs/react/js-early-return.mdx +24 -0
  54. package/docs/react/js-hoist-regexp.mdx +21 -0
  55. package/docs/react/js-index-maps.mdx +14 -0
  56. package/docs/react/js-length-check.mdx +12 -0
  57. package/docs/react/js-loop-min-max.mdx +14 -0
  58. package/docs/react/js-set-lookups.mdx +12 -0
  59. package/docs/react/js-tosorted.mdx +15 -0
  60. package/docs/react/render-activity.mdx +17 -0
  61. package/docs/react/render-conditional.mdx +11 -0
  62. package/docs/react/render-content-visibility.mdx +12 -0
  63. package/docs/react/render-cx-clsx.mdx +12 -0
  64. package/docs/react/render-hoist-jsx.mdx +16 -0
  65. package/docs/react/render-hydration-flicker.mdx +25 -0
  66. package/docs/react/render-svg-precision.mdx +17 -0
  67. package/docs/react/render-svg-wrapper.mdx +19 -0
  68. package/docs/react/rerender-defer-reads.mdx +24 -0
  69. package/docs/react/rerender-derived-state.mdx +18 -0
  70. package/docs/react/rerender-inline-objects.mdx +18 -0
  71. package/docs/react/rerender-isolate-state.mdx +36 -0
  72. package/docs/react/rerender-lazy-init.mdx +19 -0
  73. package/docs/react/rerender-memo-extract.mdx +26 -0
  74. package/docs/react/rerender-narrow-deps.mdx +26 -0
  75. package/docs/react/rerender-transitions.mdx +29 -0
  76. package/docs/react/rerender-use-client-down.mdx +34 -0
  77. package/docs/react/server-lru-cache.mdx +24 -0
  78. package/docs/react/server-parallel-fetching.mdx +24 -0
  79. package/docs/react/server-react-cache.mdx +16 -0
  80. package/docs/react/server-rsc-serialization.mdx +17 -0
  81. package/package.json +2 -1
  82. package/scripts/extract.ts +2 -2
  83. package/scripts/generate.ts +9 -2
  84. package/scripts/inject.ts +86 -23
@@ -0,0 +1,29 @@
1
+ # Cache Storage API Calls
2
+
3
+ `localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
4
+
5
+ ```tsx
6
+ const storageCache = new Map<string, string | null>()
7
+
8
+ function getLocalStorage(key: string) {
9
+ if (!storageCache.has(key)) {
10
+ storageCache.set(key, localStorage.getItem(key))
11
+ }
12
+ return storageCache.get(key)
13
+ }
14
+
15
+ function setLocalStorage(key: string, value: string) {
16
+ localStorage.setItem(key, value)
17
+ storageCache.set(key, value) // keep cache in sync
18
+ }
19
+ ```
20
+
21
+ Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just components.
22
+
23
+ Invalidate on external changes:
24
+
25
+ ```tsx
26
+ window.addEventListener('storage', (e) => {
27
+ if (e.key) storageCache.delete(e.key)
28
+ })
29
+ ```
@@ -0,0 +1,20 @@
1
+ # Combine Multiple Array Iterations
2
+
3
+ Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
4
+
5
+ ```tsx
6
+ // BAD: 3 iterations
7
+ const admins = users.filter(u => u.isAdmin)
8
+ const testers = users.filter(u => u.isTester)
9
+ const inactive = users.filter(u => !u.isActive)
10
+
11
+ // GOOD: 1 iteration
12
+ const admins: User[] = []
13
+ const testers: User[] = []
14
+ const inactive: User[] = []
15
+ for (const u of users) {
16
+ if (u.isAdmin) admins.push(u)
17
+ if (u.isTester) testers.push(u)
18
+ if (!u.isActive) inactive.push(u)
19
+ }
20
+ ```
@@ -0,0 +1,24 @@
1
+ # Early Return from Functions
2
+
3
+ Return early when result is determined to skip unnecessary processing.
4
+
5
+ ```tsx
6
+ // BAD: processes all items even after finding error
7
+ function validateUsers(users: User[]) {
8
+ let hasError = false
9
+ for (const user of users) {
10
+ if (!user.email) { hasError = true }
11
+ if (!user.name) { hasError = true }
12
+ }
13
+ return hasError ? { valid: false } : { valid: true }
14
+ }
15
+
16
+ // GOOD: returns immediately on first error
17
+ function validateUsers(users: User[]) {
18
+ for (const user of users) {
19
+ if (!user.email) return { valid: false, error: 'Email required' }
20
+ if (!user.name) return { valid: false, error: 'Name required' }
21
+ }
22
+ return { valid: true }
23
+ }
24
+ ```
@@ -0,0 +1,21 @@
1
+ # Hoist RegExp Outside Render
2
+
3
+ Don't create RegExp inside render. Hoist to module scope or memoize.
4
+
5
+ ```tsx
6
+ // BAD: new RegExp every render
7
+ function Highlighter({ text, query }) {
8
+ const regex = new RegExp(`(${query})`, 'gi')
9
+ const parts = text.split(regex)
10
+ return <>{parts.map((part, i) => ...)}</>
11
+ }
12
+
13
+ // GOOD: memoize when query is dynamic
14
+ function Highlighter({ text, query }) {
15
+ const regex = useMemo(() => new RegExp(`(${query})`, 'gi'), [query])
16
+ const parts = text.split(regex)
17
+ return <>{parts.map((part, i) => ...)}</>
18
+ }
19
+ ```
20
+
21
+ For static patterns, hoist to module scope. Note: global regexps (`/g`) have `lastIndex` state — reset before reuse or use a new instance.
@@ -0,0 +1,14 @@
1
+ # Build Index Maps for Repeated Lookups
2
+
3
+ Multiple `.find()` calls by the same key should use a Map.
4
+
5
+ ```tsx
6
+ // BAD: O(n) per lookup
7
+ orders.map(o => ({ ...o, user: users.find(u => u.id === o.userId) }))
8
+
9
+ // GOOD: O(1) per lookup
10
+ const userById = new Map(users.map(u => [u.id, u]))
11
+ orders.map(o => ({ ...o, user: userById.get(o.userId) }))
12
+ ```
13
+
14
+ Build map once (O(n)), then all lookups are O(1). For 1000 orders x 1000 users: 1M ops → 2K ops.
@@ -0,0 +1,12 @@
1
+ # Early Length Check Before Array Comparison
2
+
3
+ If lengths differ, arrays cannot be equal. Check length first to skip expensive comparisons.
4
+
5
+ ```tsx
6
+ function hasChanges(current: string[], original: string[]) {
7
+ if (current.length !== original.length) return true
8
+ const a = current.toSorted()
9
+ const b = original.toSorted()
10
+ return a.some((v, i) => v !== b[i])
11
+ }
12
+ ```
@@ -0,0 +1,14 @@
1
+ # Loop for Min/Max Instead of Sort
2
+
3
+ Finding min/max only requires a single pass. Sorting is O(n log n) overkill.
4
+
5
+ ```tsx
6
+ // BAD: O(n log n)
7
+ const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0]
8
+
9
+ // GOOD: O(n)
10
+ let latest = projects[0]
11
+ for (let i = 1; i < projects.length; i++) {
12
+ if (projects[i].updatedAt > latest.updatedAt) latest = projects[i]
13
+ }
14
+ ```
@@ -0,0 +1,12 @@
1
+ # Use Set/Map for O(1) Lookups
2
+
3
+ Convert arrays to Set/Map for repeated membership checks.
4
+
5
+ ```tsx
6
+ // BAD: O(n) per check
7
+ items.filter(item => allowedIds.includes(item.id))
8
+
9
+ // GOOD: O(1) per check
10
+ const allowed = new Set(allowedIds)
11
+ items.filter(item => allowed.has(item.id))
12
+ ```
@@ -0,0 +1,15 @@
1
+ # Use toSorted() Instead of sort()
2
+
3
+ `.sort()` mutates the array in place — breaks React state and props. Use `.toSorted()` for immutability.
4
+
5
+ ```tsx
6
+ // BAD: mutates users prop
7
+ const sorted = users.sort((a, b) => a.name.localeCompare(b.name))
8
+
9
+ // GOOD: creates new array
10
+ const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name))
11
+ ```
12
+
13
+ Also prefer: `.toReversed()`, `.toSpliced()`, `.with()` over their mutating counterparts.
14
+
15
+ Fallback for older browsers: `[...items].sort(...)`.
@@ -0,0 +1,17 @@
1
+ # Activity Component for Show/Hide
2
+
3
+ Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.
4
+
5
+ ```tsx
6
+ import { Activity } from 'react'
7
+
8
+ function Dropdown({ isOpen }: Props) {
9
+ return (
10
+ <Activity mode={isOpen ? 'visible' : 'hidden'}>
11
+ <ExpensiveMenu />
12
+ </Activity>
13
+ )
14
+ }
15
+ ```
16
+
17
+ Avoids expensive re-mount and state loss.
@@ -0,0 +1,11 @@
1
+ # Use Explicit Conditional Rendering
2
+
3
+ Use ternary (`? :`) instead of `&&` when the condition can be `0`, `NaN`, or other falsy values that render.
4
+
5
+ ```tsx
6
+ // BAD: renders "0" when count is 0
7
+ {count && <span className="badge">{count}</span>}
8
+
9
+ // GOOD: renders nothing when count is 0
10
+ {count > 0 ? <span className="badge">{count}</span> : null}
11
+ ```
@@ -0,0 +1,12 @@
1
+ # CSS content-visibility for Long Lists
2
+
3
+ Apply `content-visibility: auto` to defer off-screen rendering.
4
+
5
+ ```css
6
+ .message-item {
7
+ content-visibility: auto;
8
+ contain-intrinsic-size: 0 80px;
9
+ }
10
+ ```
11
+
12
+ For 1000 messages, browser skips layout/paint for ~990 off-screen items (10x faster initial render).
@@ -0,0 +1,12 @@
1
+ # Avoid Unnecessary cx/clsx Wrappers
2
+
3
+ ```tsx
4
+ // BAD: cx with single static class
5
+ <div className={cx("container")} />
6
+
7
+ // GOOD: plain string
8
+ <div className="container" />
9
+
10
+ // OK: cx useful for conditionals
11
+ <div className={cx("container", isActive && "active")} />
12
+ ```
@@ -0,0 +1,16 @@
1
+ # Hoist Static JSX Elements
2
+
3
+ Extract static JSX outside components to avoid re-creation. Especially helpful for large SVG nodes.
4
+
5
+ ```tsx
6
+ // BAD: recreates element every render
7
+ function Container() {
8
+ return <div>{loading && <div className="animate-pulse h-20 bg-gray-200" />}</div>
9
+ }
10
+
11
+ // GOOD: reuses same element
12
+ const skeleton = <div className="animate-pulse h-20 bg-gray-200" />
13
+ function Container() {
14
+ return <div>{loading && skeleton}</div>
15
+ }
16
+ ```
@@ -0,0 +1,25 @@
1
+ # Prevent Hydration Mismatch Without Flickering
2
+
3
+ For client-only values (theme, preferences), use an inline script that runs before React hydrates.
4
+
5
+ ```tsx
6
+ function ThemeWrapper({ children }) {
7
+ return (
8
+ <>
9
+ <div id="theme-wrapper">{children}</div>
10
+ <script dangerouslySetInnerHTML={{ __html: `
11
+ (function() {
12
+ try {
13
+ var t = localStorage.getItem('theme') || 'light';
14
+ document.getElementById('theme-wrapper').className = t;
15
+ } catch(e) {}
16
+ })();
17
+ `}} />
18
+ </>
19
+ )
20
+ }
21
+ ```
22
+
23
+ The inline script executes synchronously before showing the element. No flickering, no hydration mismatch.
24
+
25
+ Useful for: theme toggles, user preferences, authentication states, any client-only data that should render immediately.
@@ -0,0 +1,17 @@
1
+ # Optimize SVG Precision
2
+
3
+ Reduce SVG coordinate precision to decrease file size.
4
+
5
+ ```svg
6
+ <!-- BAD: excessive precision -->
7
+ <path d="M 10.293847 20.847362 L 30.938472 40.192837" />
8
+
9
+ <!-- GOOD: 1 decimal place -->
10
+ <path d="M 10.3 20.8 L 30.9 40.2" />
11
+ ```
12
+
13
+ Automate with SVGO:
14
+
15
+ ```bash
16
+ npx svgo --precision=1 --multipass icon.svg
17
+ ```
@@ -0,0 +1,19 @@
1
+ # Animate SVG Wrapper, Not SVG Element
2
+
3
+ Many browsers don't hardware-accelerate CSS animations on SVG elements. Wrap in a `<div>`.
4
+
5
+ ```tsx
6
+ // BAD: no hardware acceleration
7
+ <svg className="animate-spin" width="24" height="24" viewBox="0 0 24 24">
8
+ <circle cx="12" cy="12" r="10" stroke="currentColor" />
9
+ </svg>
10
+
11
+ // GOOD: GPU-accelerated
12
+ <div className="animate-spin">
13
+ <svg width="24" height="24" viewBox="0 0 24 24">
14
+ <circle cx="12" cy="12" r="10" stroke="currentColor" />
15
+ </svg>
16
+ </div>
17
+ ```
18
+
19
+ Applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`).
@@ -0,0 +1,24 @@
1
+ # Defer State Reads to Usage Point
2
+
3
+ Don't subscribe to dynamic state if you only read it inside callbacks.
4
+
5
+ ```tsx
6
+ // BAD: subscribes to all searchParams changes
7
+ function ShareButton({ chatId }: { chatId: string }) {
8
+ const searchParams = useSearchParams()
9
+ const handleShare = () => {
10
+ const ref = searchParams.get('ref')
11
+ shareChat(chatId, { ref })
12
+ }
13
+ return <button onClick={handleShare}>Share</button>
14
+ }
15
+
16
+ // GOOD: reads on demand, no subscription
17
+ function ShareButton({ chatId }: { chatId: string }) {
18
+ const handleShare = () => {
19
+ const ref = new URLSearchParams(window.location.search).get('ref')
20
+ shareChat(chatId, { ref })
21
+ }
22
+ return <button onClick={handleShare}>Share</button>
23
+ }
24
+ ```
@@ -0,0 +1,18 @@
1
+ # Subscribe to Derived State
2
+
3
+ Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
4
+
5
+ ```tsx
6
+ // BAD: re-renders on every pixel change
7
+ function Sidebar() {
8
+ const width = useWindowWidth()
9
+ const isMobile = width < 768
10
+ return <nav className={isMobile ? 'mobile' : 'desktop'} />
11
+ }
12
+
13
+ // GOOD: re-renders only when boolean changes
14
+ function Sidebar() {
15
+ const isMobile = useMediaQuery('(max-width: 767px)')
16
+ return <nav className={isMobile ? 'mobile' : 'desktop'} />
17
+ }
18
+ ```
@@ -0,0 +1,18 @@
1
+ # Avoid Inline Objects in JSX
2
+
3
+ Inline objects create a new reference every render, causing child re-renders.
4
+
5
+ ```tsx
6
+ // BAD: new reference every render
7
+ <Component style={{ padding: 16 }} />
8
+ <Component data={{ id: 1, name: 'Test' }} />
9
+
10
+ // GOOD: stable reference
11
+ const style = { padding: 16 }
12
+ const data = useMemo(() => ({ id: 1, name: 'Test' }), [])
13
+ <Component style={style} />
14
+ <Component data={data} />
15
+
16
+ // BEST: Tailwind
17
+ <Component className="p-4" />
18
+ ```
@@ -0,0 +1,36 @@
1
+ # Isolate State to Smallest Component
2
+
3
+ Parent components should not re-render when child state changes.
4
+
5
+ ```tsx
6
+ // BAD: parent re-renders when count changes
7
+ function Parent() {
8
+ const [count, setCount] = useState(0)
9
+ return (
10
+ <div>
11
+ <button onClick={() => setCount(c => c + 1)}>{count}</button>
12
+ <ExpensiveList />
13
+ </div>
14
+ )
15
+ }
16
+
17
+ // GOOD: state isolated in child
18
+ function Parent() {
19
+ return (
20
+ <div>
21
+ <Counter />
22
+ <ExpensiveList />
23
+ </div>
24
+ )
25
+ }
26
+ ```
27
+
28
+ Use the children pattern to prevent re-renders:
29
+
30
+ ```tsx
31
+ function StatefulWrapper({ children }) {
32
+ const [state, setState] = useState(false)
33
+ return <div onClick={() => setState(!state)}>{children}</div>
34
+ }
35
+ // Usage: <StatefulWrapper><ExpensiveChild /></StatefulWrapper>
36
+ ```
@@ -0,0 +1,19 @@
1
+ # Use Lazy State Initialization
2
+
3
+ Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render.
4
+
5
+ ```tsx
6
+ // BAD: JSON.parse runs on every render
7
+ const [settings, setSettings] = useState(
8
+ JSON.parse(localStorage.getItem('settings') || '{}')
9
+ )
10
+
11
+ // GOOD: runs only once
12
+ const [settings, setSettings] = useState(() =>
13
+ JSON.parse(localStorage.getItem('settings') || '{}')
14
+ )
15
+ ```
16
+
17
+ Use for: localStorage/sessionStorage reads, building data structures, DOM reads, heavy transformations.
18
+
19
+ Skip for: simple primitives (`useState(0)`), direct references (`useState(props.value)`), cheap literals (`useState({})`).
@@ -0,0 +1,26 @@
1
+ # Extract to Memoized Components
2
+
3
+ Extract expensive work into memoized components to enable early returns before computation.
4
+
5
+ ```tsx
6
+ // BAD: computes avatar even when loading
7
+ function Profile({ user, loading }: Props) {
8
+ const avatar = useMemo(() => {
9
+ const id = computeAvatarId(user)
10
+ return <Avatar id={id} />
11
+ }, [user])
12
+ if (loading) return <Skeleton />
13
+ return <div>{avatar}</div>
14
+ }
15
+
16
+ // GOOD: skips computation when loading
17
+ const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
18
+ const id = useMemo(() => computeAvatarId(user), [user])
19
+ return <Avatar id={id} />
20
+ })
21
+
22
+ function Profile({ user, loading }: Props) {
23
+ if (loading) return <Skeleton />
24
+ return <div><UserAvatar user={user} /></div>
25
+ }
26
+ ```
@@ -0,0 +1,26 @@
1
+ # Narrow Effect Dependencies
2
+
3
+ Specify primitive dependencies instead of objects to minimize effect re-runs.
4
+
5
+ ```tsx
6
+ // BAD: re-runs on any user field change
7
+ useEffect(() => { console.log(user.id) }, [user])
8
+
9
+ // GOOD: re-runs only when id changes
10
+ useEffect(() => { console.log(user.id) }, [user.id])
11
+ ```
12
+
13
+ For derived state, compute outside the effect:
14
+
15
+ ```tsx
16
+ // BAD: runs on width=767, 766, 765...
17
+ useEffect(() => {
18
+ if (width < 768) enableMobileMode()
19
+ }, [width])
20
+
21
+ // GOOD: runs only on boolean transition
22
+ const isMobile = width < 768
23
+ useEffect(() => {
24
+ if (isMobile) enableMobileMode()
25
+ }, [isMobile])
26
+ ```
@@ -0,0 +1,29 @@
1
+ # Use Transitions for Non-Urgent Updates
2
+
3
+ Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
4
+
5
+ ```tsx
6
+ // BAD: blocks UI on every scroll
7
+ function ScrollTracker() {
8
+ const [scrollY, setScrollY] = useState(0)
9
+ useEffect(() => {
10
+ const handler = () => setScrollY(window.scrollY)
11
+ window.addEventListener('scroll', handler, { passive: true })
12
+ return () => window.removeEventListener('scroll', handler)
13
+ }, [])
14
+ }
15
+
16
+ // GOOD: non-blocking updates
17
+ import { startTransition } from 'react'
18
+
19
+ function ScrollTracker() {
20
+ const [scrollY, setScrollY] = useState(0)
21
+ useEffect(() => {
22
+ const handler = () => {
23
+ startTransition(() => setScrollY(window.scrollY))
24
+ }
25
+ window.addEventListener('scroll', handler, { passive: true })
26
+ return () => window.removeEventListener('scroll', handler)
27
+ }, [])
28
+ }
29
+ ```
@@ -0,0 +1,34 @@
1
+ # Push "use client" Down
2
+
3
+ Keep parent as Server Component. Only mark the smallest interactive leaf as Client Component.
4
+
5
+ ```tsx
6
+ // BAD: entire page is client-side
7
+ "use client"
8
+ export default function ProductPage({ product }) {
9
+ return (
10
+ <div>
11
+ <h1>{product.name}</h1>
12
+ <AddToCartButton product={product} />
13
+ </div>
14
+ )
15
+ }
16
+
17
+ // GOOD: only interactive part is client
18
+ export default function ProductPage({ product }) {
19
+ return (
20
+ <div>
21
+ <h1>{product.name}</h1>
22
+ <AddToCartButton product={product} />
23
+ </div>
24
+ )
25
+ }
26
+ // AddToCartButton.tsx
27
+ "use client"
28
+ export function AddToCartButton({ product }) {
29
+ const [loading, setLoading] = useState(false)
30
+ return <button onClick={() => addToCart(product)}>Add to Cart</button>
31
+ }
32
+ ```
33
+
34
+ Only use "use client" when the component uses hooks, event handlers, or browser-only APIs.
@@ -0,0 +1,24 @@
1
+ # Cross-Request LRU Caching
2
+
3
+ `React.cache()` only works within one request. For data shared across sequential requests, use an LRU cache.
4
+
5
+ ```tsx
6
+ import { LRUCache } from 'lru-cache'
7
+
8
+ const cache = new LRUCache<string, any>({
9
+ max: 1000,
10
+ ttl: 5 * 60 * 1000, // 5 minutes
11
+ })
12
+
13
+ export async function getUser(id: string) {
14
+ const cached = cache.get(id)
15
+ if (cached) return cached
16
+ const user = await db.user.findUnique({ where: { id } })
17
+ cache.set(id, user)
18
+ return user
19
+ }
20
+ // Request 1: DB query, result cached
21
+ // Request 2: cache hit, no DB query
22
+ ```
23
+
24
+ In serverless, consider Redis for cross-process caching.
@@ -0,0 +1,24 @@
1
+ # Parallel Data Fetching with Component Composition
2
+
3
+ React Server Components execute sequentially within a tree. Restructure with composition to parallelize.
4
+
5
+ ```tsx
6
+ // BAD: Sidebar waits for Header's fetch
7
+ export default async function Page() {
8
+ const header = await fetchHeader()
9
+ return <div><div>{header}</div><Sidebar /></div>
10
+ }
11
+
12
+ // GOOD: both fetch simultaneously
13
+ async function Header() {
14
+ const data = await fetchHeader()
15
+ return <div>{data}</div>
16
+ }
17
+ async function Sidebar() {
18
+ const items = await fetchSidebarItems()
19
+ return <nav>{items.map(renderItem)}</nav>
20
+ }
21
+ export default function Page() {
22
+ return <div><Header /><Sidebar /></div>
23
+ }
24
+ ```
@@ -0,0 +1,16 @@
1
+ # Per-Request Deduplication with React.cache()
2
+
3
+ Use `React.cache()` for server-side request deduplication. Auth and DB queries benefit most.
4
+
5
+ ```tsx
6
+ import { cache } from 'react'
7
+
8
+ export const getCurrentUser = cache(async () => {
9
+ const session = await auth()
10
+ if (!session?.user?.id) return null
11
+ return db.user.findUnique({ where: { id: session.user.id } })
12
+ })
13
+ // Multiple calls in one request → single query
14
+ ```
15
+
16
+ `React.cache()` only deduplicates within a single request. For cross-request caching, use LRU cache.
@@ -0,0 +1,17 @@
1
+ # Minimize Serialization at RSC Boundaries
2
+
3
+ The React Server/Client boundary serializes all object properties. Only pass fields the client actually uses.
4
+
5
+ ```tsx
6
+ // BAD: serializes all 50 fields
7
+ async function Page() {
8
+ const user = await fetchUser()
9
+ return <Profile user={user} />
10
+ }
11
+
12
+ // GOOD: serializes 1 field
13
+ async function Page() {
14
+ const user = await fetchUser()
15
+ return <Profile name={user.name} />
16
+ }
17
+ ```
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@numbered/docs-to-context",
3
- "version": "0.1.5",
3
+ "version": "0.3.0",
4
4
  "description": "Generate project docs (component APIs, design system, architecture) and inject index into CLAUDE.md",
5
5
  "bin": {
6
6
  "docs-to-context": "scripts/generate.ts"
7
7
  },
8
8
  "files": [
9
9
  "scripts/*.ts",
10
+ "docs/**/*.mdx",
10
11
  "README.md"
11
12
  ],
12
13
  "scripts": {