create-modern-react 2.1.2 → 2.1.3

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 (102) hide show
  1. package/README.md +19 -0
  2. package/lib/prompts.js +1 -0
  3. package/lib/setup.js +3 -0
  4. package/package.json +1 -1
  5. package/templates/base/.claude/skills/agent-browser/SKILL.md +356 -0
  6. package/templates/base/.claude/skills/agent-browser/references/authentication.md +188 -0
  7. package/templates/base/.claude/skills/agent-browser/references/proxy-support.md +175 -0
  8. package/templates/base/.claude/skills/agent-browser/references/session-management.md +181 -0
  9. package/templates/base/.claude/skills/agent-browser/references/snapshot-refs.md +186 -0
  10. package/templates/base/.claude/skills/agent-browser/references/video-recording.md +162 -0
  11. package/templates/base/.claude/skills/agent-browser/templates/authenticated-session.sh +91 -0
  12. package/templates/base/.claude/skills/agent-browser/templates/capture-workflow.sh +68 -0
  13. package/templates/base/.claude/skills/agent-browser/templates/form-automation.sh +64 -0
  14. package/templates/base/.claude/skills/autoskill/skill.md +134 -0
  15. package/templates/base/.claude/skills/design-principles/skill.md +237 -0
  16. package/templates/base/.claude/skills/frontend-design/skill.md +42 -0
  17. package/templates/base/.claude/skills/learn-together/skill.md +448 -0
  18. package/templates/base/.claude/skills/question-me/skill.md +175 -0
  19. package/templates/base/.claude/skills/react-best-practices/AGENTS.md +2410 -0
  20. package/templates/base/.claude/skills/react-best-practices/README.md +123 -0
  21. package/templates/base/.claude/skills/react-best-practices/SKILL.md +135 -0
  22. package/templates/base/.claude/skills/react-best-practices/metadata.json +15 -0
  23. package/templates/base/.claude/skills/react-best-practices/rules/_sections.md +46 -0
  24. package/templates/base/.claude/skills/react-best-practices/rules/_template.md +28 -0
  25. package/templates/base/.claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  26. package/templates/base/.claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  27. package/templates/base/.claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
  28. package/templates/base/.claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
  29. package/templates/base/.claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
  30. package/templates/base/.claude/skills/react-best-practices/rules/async-parallel.md +28 -0
  31. package/templates/base/.claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  32. package/templates/base/.claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  33. package/templates/base/.claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  34. package/templates/base/.claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  35. package/templates/base/.claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  36. package/templates/base/.claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
  37. package/templates/base/.claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  38. package/templates/base/.claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
  39. package/templates/base/.claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  40. package/templates/base/.claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  41. package/templates/base/.claude/skills/react-best-practices/rules/js-batch-dom-css.md +57 -0
  42. package/templates/base/.claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  43. package/templates/base/.claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  44. package/templates/base/.claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  45. package/templates/base/.claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  46. package/templates/base/.claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
  47. package/templates/base/.claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  48. package/templates/base/.claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
  49. package/templates/base/.claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  50. package/templates/base/.claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  51. package/templates/base/.claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  52. package/templates/base/.claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  53. package/templates/base/.claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
  54. package/templates/base/.claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  55. package/templates/base/.claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  56. package/templates/base/.claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  57. package/templates/base/.claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  58. package/templates/base/.claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  59. package/templates/base/.claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  60. package/templates/base/.claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  61. package/templates/base/.claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  62. package/templates/base/.claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  63. package/templates/base/.claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  64. package/templates/base/.claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  65. package/templates/base/.claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
  66. package/templates/base/.claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  67. package/templates/base/.claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  68. package/templates/base/.claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  69. package/templates/base/.claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
  70. package/templates/base/.claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  71. package/templates/base/.claude/skills/react-best-practices/rules/server-serialization.md +38 -0
  72. package/templates/base/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
  73. package/templates/base/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  74. package/templates/base/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  75. package/templates/base/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
  76. package/templates/base/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  77. package/templates/base/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  78. package/templates/base/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  79. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  80. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  81. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  82. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  83. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  84. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  85. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  86. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +53 -0
  87. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  88. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  89. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  90. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  91. package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  92. package/templates/base/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
  93. package/templates/base/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  94. package/templates/base/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  95. package/templates/base/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  96. package/templates/base/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  97. package/templates/base/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
  98. package/templates/base/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  99. package/templates/base/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -0
  100. package/templates/base/public/robots.txt +2 -0
  101. package/templates/base/public/vite.svg +1 -0
  102. package/templates/base/src/screens/home/index.tsx +15 -2
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: Strategic Suspense Boundaries
3
+ impact: HIGH
4
+ impactDescription: faster initial paint
5
+ tags: async, suspense, streaming, layout-shift
6
+ ---
7
+
8
+ ## Strategic Suspense Boundaries
9
+
10
+ Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
11
+
12
+ **Incorrect (wrapper blocked by data fetching):**
13
+
14
+ ```tsx
15
+ async function Page() {
16
+ const data = await fetchData() // Blocks entire page
17
+
18
+ return (
19
+ <div>
20
+ <div>Sidebar</div>
21
+ <div>Header</div>
22
+ <div>
23
+ <DataDisplay data={data} />
24
+ </div>
25
+ <div>Footer</div>
26
+ </div>
27
+ )
28
+ }
29
+ ```
30
+
31
+ The entire layout waits for data even though only the middle section needs it.
32
+
33
+ **Correct (wrapper shows immediately, data streams in):**
34
+
35
+ ```tsx
36
+ function Page() {
37
+ return (
38
+ <div>
39
+ <div>Sidebar</div>
40
+ <div>Header</div>
41
+ <div>
42
+ <Suspense fallback={<Skeleton />}>
43
+ <DataDisplay />
44
+ </Suspense>
45
+ </div>
46
+ <div>Footer</div>
47
+ </div>
48
+ )
49
+ }
50
+
51
+ async function DataDisplay() {
52
+ const data = await fetchData() // Only blocks this component
53
+ return <div>{data.content}</div>
54
+ }
55
+ ```
56
+
57
+ Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
58
+
59
+ **Alternative (share promise across components):**
60
+
61
+ ```tsx
62
+ function Page() {
63
+ // Start fetch immediately, but don't await
64
+ const dataPromise = fetchData()
65
+
66
+ return (
67
+ <div>
68
+ <div>Sidebar</div>
69
+ <div>Header</div>
70
+ <Suspense fallback={<Skeleton />}>
71
+ <DataDisplay dataPromise={dataPromise} />
72
+ <DataSummary dataPromise={dataPromise} />
73
+ </Suspense>
74
+ <div>Footer</div>
75
+ </div>
76
+ )
77
+ }
78
+
79
+ function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
80
+ const data = use(dataPromise) // Unwraps the promise
81
+ return <div>{data.content}</div>
82
+ }
83
+
84
+ function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
85
+ const data = use(dataPromise) // Reuses the same promise
86
+ return <div>{data.summary}</div>
87
+ }
88
+ ```
89
+
90
+ Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
91
+
92
+ **When NOT to use this pattern:**
93
+
94
+ - Critical data needed for layout decisions (affects positioning)
95
+ - SEO-critical content above the fold
96
+ - Small, fast queries where suspense overhead isn't worth it
97
+ - When you want to avoid layout shift (loading → content jump)
98
+
99
+ **Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Avoid Barrel File Imports
3
+ impact: CRITICAL
4
+ impactDescription: 200-800ms import cost, slow builds
5
+ tags: bundle, imports, tree-shaking, barrel-files, performance
6
+ ---
7
+
8
+ ## Avoid Barrel File Imports
9
+
10
+ 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'`).
11
+
12
+ 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.
13
+
14
+ **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.
15
+
16
+ **Incorrect (imports entire library):**
17
+
18
+ ```tsx
19
+ import { Check, X, Menu } from 'lucide-react'
20
+ // Loads 1,583 modules, takes ~2.8s extra in dev
21
+ // Runtime cost: 200-800ms on every cold start
22
+
23
+ import { Button, TextField } from '@mui/material'
24
+ // Loads 2,225 modules, takes ~4.2s extra in dev
25
+ ```
26
+
27
+ **Correct (imports only what you need):**
28
+
29
+ ```tsx
30
+ import Check from 'lucide-react/dist/esm/icons/check'
31
+ import X from 'lucide-react/dist/esm/icons/x'
32
+ import Menu from 'lucide-react/dist/esm/icons/menu'
33
+ // Loads only 3 modules (~2KB vs ~1MB)
34
+
35
+ import Button from '@mui/material/Button'
36
+ import TextField from '@mui/material/TextField'
37
+ // Loads only what you use
38
+ ```
39
+
40
+ **Alternative (Next.js 13.5+):**
41
+
42
+ ```js
43
+ // next.config.js - use optimizePackageImports
44
+ module.exports = {
45
+ experimental: {
46
+ optimizePackageImports: ['lucide-react', '@mui/material']
47
+ }
48
+ }
49
+
50
+ // Then you can keep the ergonomic barrel imports:
51
+ import { Check, X, Menu } from 'lucide-react'
52
+ // Automatically transformed to direct imports at build time
53
+ ```
54
+
55
+ Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
56
+
57
+ 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`.
58
+
59
+ Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Conditional Module Loading
3
+ impact: HIGH
4
+ impactDescription: loads large data only when needed
5
+ tags: bundle, conditional-loading, lazy-loading
6
+ ---
7
+
8
+ ## Conditional Module Loading
9
+
10
+ Load large data or modules only when a feature is activated.
11
+
12
+ **Example (lazy-load animation frames):**
13
+
14
+ ```tsx
15
+ function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {
16
+ const [frames, setFrames] = useState<Frame[] | null>(null)
17
+
18
+ useEffect(() => {
19
+ if (enabled && !frames && typeof window !== 'undefined') {
20
+ import('./animation-frames.js')
21
+ .then(mod => setFrames(mod.frames))
22
+ .catch(() => setEnabled(false))
23
+ }
24
+ }, [enabled, frames, setEnabled])
25
+
26
+ if (!frames) return <Skeleton />
27
+ return <Canvas frames={frames} />
28
+ }
29
+ ```
30
+
31
+ The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Defer Non-Critical Third-Party Libraries
3
+ impact: MEDIUM
4
+ impactDescription: loads after hydration
5
+ tags: bundle, third-party, analytics, defer
6
+ ---
7
+
8
+ ## Defer Non-Critical Third-Party Libraries
9
+
10
+ Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
11
+
12
+ **Incorrect (blocks initial bundle):**
13
+
14
+ ```tsx
15
+ import { Analytics } from '@vercel/analytics/react'
16
+
17
+ export default function RootLayout({ children }) {
18
+ return (
19
+ <html>
20
+ <body>
21
+ {children}
22
+ <Analytics />
23
+ </body>
24
+ </html>
25
+ )
26
+ }
27
+ ```
28
+
29
+ **Correct (loads after hydration):**
30
+
31
+ ```tsx
32
+ import dynamic from 'next/dynamic'
33
+
34
+ const Analytics = dynamic(
35
+ () => import('@vercel/analytics/react').then(m => m.Analytics),
36
+ { ssr: false }
37
+ )
38
+
39
+ export default function RootLayout({ children }) {
40
+ return (
41
+ <html>
42
+ <body>
43
+ {children}
44
+ <Analytics />
45
+ </body>
46
+ </html>
47
+ )
48
+ }
49
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Dynamic Imports for Heavy Components
3
+ impact: CRITICAL
4
+ impactDescription: directly affects TTI and LCP
5
+ tags: bundle, dynamic-import, code-splitting, next-dynamic
6
+ ---
7
+
8
+ ## Dynamic Imports for Heavy Components
9
+
10
+ Use `next/dynamic` to lazy-load large components not needed on initial render.
11
+
12
+ **Incorrect (Monaco bundles with main chunk ~300KB):**
13
+
14
+ ```tsx
15
+ import { MonacoEditor } from './monaco-editor'
16
+
17
+ function CodePanel({ code }: { code: string }) {
18
+ return <MonacoEditor value={code} />
19
+ }
20
+ ```
21
+
22
+ **Correct (Monaco loads on demand):**
23
+
24
+ ```tsx
25
+ import dynamic from 'next/dynamic'
26
+
27
+ const MonacoEditor = dynamic(
28
+ () => import('./monaco-editor').then(m => m.MonacoEditor),
29
+ { ssr: false }
30
+ )
31
+
32
+ function CodePanel({ code }: { code: string }) {
33
+ return <MonacoEditor value={code} />
34
+ }
35
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Preload Based on User Intent
3
+ impact: MEDIUM
4
+ impactDescription: reduces perceived latency
5
+ tags: bundle, preload, user-intent, hover
6
+ ---
7
+
8
+ ## Preload Based on User Intent
9
+
10
+ Preload heavy bundles before they're needed to reduce perceived latency.
11
+
12
+ **Example (preload on hover/focus):**
13
+
14
+ ```tsx
15
+ function EditorButton({ onClick }: { onClick: () => void }) {
16
+ const preload = () => {
17
+ if (typeof window !== 'undefined') {
18
+ void import('./monaco-editor')
19
+ }
20
+ }
21
+
22
+ return (
23
+ <button
24
+ onMouseEnter={preload}
25
+ onFocus={preload}
26
+ onClick={onClick}
27
+ >
28
+ Open Editor
29
+ </button>
30
+ )
31
+ }
32
+ ```
33
+
34
+ **Example (preload when feature flag is enabled):**
35
+
36
+ ```tsx
37
+ function FlagsProvider({ children, flags }: Props) {
38
+ useEffect(() => {
39
+ if (flags.editorEnabled && typeof window !== 'undefined') {
40
+ void import('./monaco-editor').then(mod => mod.init())
41
+ }
42
+ }, [flags.editorEnabled])
43
+
44
+ return <FlagsContext.Provider value={flags}>
45
+ {children}
46
+ </FlagsContext.Provider>
47
+ }
48
+ ```
49
+
50
+ The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: Deduplicate Global Event Listeners
3
+ impact: LOW
4
+ impactDescription: single listener for N components
5
+ tags: client, swr, event-listeners, subscription
6
+ ---
7
+
8
+ ## Deduplicate Global Event Listeners
9
+
10
+ Use `useSWRSubscription()` to share global event listeners across component instances.
11
+
12
+ **Incorrect (N instances = N listeners):**
13
+
14
+ ```tsx
15
+ function useKeyboardShortcut(key: string, callback: () => void) {
16
+ useEffect(() => {
17
+ const handler = (e: KeyboardEvent) => {
18
+ if (e.metaKey && e.key === key) {
19
+ callback()
20
+ }
21
+ }
22
+ window.addEventListener('keydown', handler)
23
+ return () => window.removeEventListener('keydown', handler)
24
+ }, [key, callback])
25
+ }
26
+ ```
27
+
28
+ When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
29
+
30
+ **Correct (N instances = 1 listener):**
31
+
32
+ ```tsx
33
+ import useSWRSubscription from 'swr/subscription'
34
+
35
+ // Module-level Map to track callbacks per key
36
+ const keyCallbacks = new Map<string, Set<() => void>>()
37
+
38
+ function useKeyboardShortcut(key: string, callback: () => void) {
39
+ // Register this callback in the Map
40
+ useEffect(() => {
41
+ if (!keyCallbacks.has(key)) {
42
+ keyCallbacks.set(key, new Set())
43
+ }
44
+ keyCallbacks.get(key)!.add(callback)
45
+
46
+ return () => {
47
+ const set = keyCallbacks.get(key)
48
+ if (set) {
49
+ set.delete(callback)
50
+ if (set.size === 0) {
51
+ keyCallbacks.delete(key)
52
+ }
53
+ }
54
+ }
55
+ }, [key, callback])
56
+
57
+ useSWRSubscription('global-keydown', () => {
58
+ const handler = (e: KeyboardEvent) => {
59
+ if (e.metaKey && keyCallbacks.has(e.key)) {
60
+ keyCallbacks.get(e.key)!.forEach(cb => cb())
61
+ }
62
+ }
63
+ window.addEventListener('keydown', handler)
64
+ return () => window.removeEventListener('keydown', handler)
65
+ })
66
+ }
67
+
68
+ function Profile() {
69
+ // Multiple shortcuts will share the same listener
70
+ useKeyboardShortcut('p', () => { /* ... */ })
71
+ useKeyboardShortcut('k', () => { /* ... */ })
72
+ // ...
73
+ }
74
+ ```
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Version and Minimize localStorage Data
3
+ impact: MEDIUM
4
+ impactDescription: prevents schema conflicts, reduces storage size
5
+ tags: client, localStorage, storage, versioning, data-minimization
6
+ ---
7
+
8
+ ## Version and Minimize localStorage Data
9
+
10
+ Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ // No version, stores everything, no error handling
16
+ localStorage.setItem('userConfig', JSON.stringify(fullUserObject))
17
+ const data = localStorage.getItem('userConfig')
18
+ ```
19
+
20
+ **Correct:**
21
+
22
+ ```typescript
23
+ const VERSION = 'v2'
24
+
25
+ function saveConfig(config: { theme: string; language: string }) {
26
+ try {
27
+ localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
28
+ } catch {
29
+ // Throws in incognito/private browsing, quota exceeded, or disabled
30
+ }
31
+ }
32
+
33
+ function loadConfig() {
34
+ try {
35
+ const data = localStorage.getItem(`userConfig:${VERSION}`)
36
+ return data ? JSON.parse(data) : null
37
+ } catch {
38
+ return null
39
+ }
40
+ }
41
+
42
+ // Migration from v1 to v2
43
+ function migrate() {
44
+ try {
45
+ const v1 = localStorage.getItem('userConfig:v1')
46
+ if (v1) {
47
+ const old = JSON.parse(v1)
48
+ saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
49
+ localStorage.removeItem('userConfig:v1')
50
+ }
51
+ } catch {}
52
+ }
53
+ ```
54
+
55
+ **Store minimal fields from server responses:**
56
+
57
+ ```typescript
58
+ // User object has 20+ fields, only store what UI needs
59
+ function cachePrefs(user: FullUser) {
60
+ try {
61
+ localStorage.setItem('prefs:v1', JSON.stringify({
62
+ theme: user.preferences.theme,
63
+ notifications: user.preferences.notifications
64
+ }))
65
+ } catch {}
66
+ }
67
+ ```
68
+
69
+ **Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
70
+
71
+ **Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Use Passive Event Listeners for Scrolling Performance
3
+ impact: MEDIUM
4
+ impactDescription: eliminates scroll delay caused by event listeners
5
+ tags: client, event-listeners, scrolling, performance, touch, wheel
6
+ ---
7
+
8
+ ## Use Passive Event Listeners for Scrolling Performance
9
+
10
+ Add `{ 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.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ useEffect(() => {
16
+ const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
17
+ const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
18
+
19
+ document.addEventListener('touchstart', handleTouch)
20
+ document.addEventListener('wheel', handleWheel)
21
+
22
+ return () => {
23
+ document.removeEventListener('touchstart', handleTouch)
24
+ document.removeEventListener('wheel', handleWheel)
25
+ }
26
+ }, [])
27
+ ```
28
+
29
+ **Correct:**
30
+
31
+ ```typescript
32
+ useEffect(() => {
33
+ const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
34
+ const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
35
+
36
+ document.addEventListener('touchstart', handleTouch, { passive: true })
37
+ document.addEventListener('wheel', handleWheel, { passive: true })
38
+
39
+ return () => {
40
+ document.removeEventListener('touchstart', handleTouch)
41
+ document.removeEventListener('wheel', handleWheel)
42
+ }
43
+ }, [])
44
+ ```
45
+
46
+ **Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.
47
+
48
+ **Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.
@@ -0,0 +1,56 @@
1
+ ---
2
+ title: Use SWR for Automatic Deduplication
3
+ impact: MEDIUM-HIGH
4
+ impactDescription: automatic deduplication
5
+ tags: client, swr, deduplication, data-fetching
6
+ ---
7
+
8
+ ## Use SWR for Automatic Deduplication
9
+
10
+ SWR enables request deduplication, caching, and revalidation across component instances.
11
+
12
+ **Incorrect (no deduplication, each instance fetches):**
13
+
14
+ ```tsx
15
+ function UserList() {
16
+ const [users, setUsers] = useState([])
17
+ useEffect(() => {
18
+ fetch('/api/users')
19
+ .then(r => r.json())
20
+ .then(setUsers)
21
+ }, [])
22
+ }
23
+ ```
24
+
25
+ **Correct (multiple instances share one request):**
26
+
27
+ ```tsx
28
+ import useSWR from 'swr'
29
+
30
+ function UserList() {
31
+ const { data: users } = useSWR('/api/users', fetcher)
32
+ }
33
+ ```
34
+
35
+ **For immutable data:**
36
+
37
+ ```tsx
38
+ import { useImmutableSWR } from '@/lib/swr'
39
+
40
+ function StaticContent() {
41
+ const { data } = useImmutableSWR('/api/config', fetcher)
42
+ }
43
+ ```
44
+
45
+ **For mutations:**
46
+
47
+ ```tsx
48
+ import { useSWRMutation } from 'swr/mutation'
49
+
50
+ function UpdateButton() {
51
+ const { trigger } = useSWRMutation('/api/user', updateUser)
52
+ return <button onClick={() => trigger()}>Update</button>
53
+ }
54
+ ```
55
+
56
+ Reference: [https://swr.vercel.app](https://swr.vercel.app)
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: Batch DOM CSS Changes
3
+ impact: MEDIUM
4
+ impactDescription: reduces reflows/repaints
5
+ tags: javascript, dom, css, performance, reflow
6
+ ---
7
+
8
+ ## Batch DOM CSS Changes
9
+
10
+ Avoid 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.
11
+
12
+ **Incorrect (interleaved reads and writes force reflows):**
13
+
14
+ ```typescript
15
+ function updateElementStyles(element: HTMLElement) {
16
+ element.style.width = '100px'
17
+ const width = element.offsetWidth // Forces reflow
18
+ element.style.height = '200px'
19
+ const height = element.offsetHeight // Forces another reflow
20
+ }
21
+ ```
22
+
23
+ **Correct (batch writes, then read once):**
24
+
25
+ ```typescript
26
+ function updateElementStyles(element: HTMLElement) {
27
+ // Batch all writes together
28
+ element.style.width = '100px'
29
+ element.style.height = '200px'
30
+ element.style.backgroundColor = 'blue'
31
+ element.style.border = '1px solid black'
32
+
33
+ // Read after all writes are done (single reflow)
34
+ const { width, height } = element.getBoundingClientRect()
35
+ }
36
+ ```
37
+
38
+ **Better: use CSS classes**
39
+
40
+ ```css
41
+ .highlighted-box {
42
+ width: 100px;
43
+ height: 200px;
44
+ background-color: blue;
45
+ border: 1px solid black;
46
+ }
47
+ ```
48
+
49
+ ```typescript
50
+ function updateElementStyles(element: HTMLElement) {
51
+ element.classList.add('highlighted-box')
52
+
53
+ const { width, height } = element.getBoundingClientRect()
54
+ }
55
+ ```
56
+
57
+ Prefer 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.