@decocms/start 0.19.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 (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,468 @@
1
+ ---
2
+ name: deco-tanstack-hydration-fixes
3
+ description: Fix hydration mismatches, flash-of-white, CLS from third-party scripts, scroll-to-top bugs, and React DOM warnings in Deco storefronts on TanStack Start/React/Cloudflare Workers. Use when debugging hydration errors, page flicker during navigation, blank screen on F5, layout shifts from external scripts, or React console warnings about invalid DOM properties.
4
+ ---
5
+
6
+ # Hydration & Navigation Fixes for Deco TanStack Storefronts
7
+
8
+ Patterns and fixes for hydration mismatches, flash-of-white, CLS, scroll issues, and React warnings discovered in production Deco storefronts running TanStack Start + React 19 + Cloudflare Workers.
9
+
10
+ ## 1. Flash-of-White / Blank Screen on F5
11
+
12
+ **Symptom**: Page loads, goes blank for a moment, then content reappears.
13
+
14
+ **Root cause**: `React.lazy` + `<Suspense>` for eager (above-the-fold) sections. Even with `syncThenable` optimization, the module may not be synchronously available during hydration, causing React to show the fallback and discard server HTML.
15
+
16
+ ### Fix: Synchronous Section Registry
17
+
18
+ Register critical above-the-fold sections with static imports so they never go through `React.lazy`:
19
+
20
+ ```typescript
21
+ // site setup.ts
22
+ import { registerSectionsSync } from "@decocms/start/cms";
23
+ import HeaderSection from "./sections/Header/Header";
24
+ import FooterSection from "./sections/Footer/Footer";
25
+ import ThemeSection from "./sections/Theme/Theme";
26
+
27
+ registerSectionsSync({
28
+ "site/sections/Header/Header.tsx": HeaderSection,
29
+ "site/sections/Footer/Footer.tsx": FooterSection,
30
+ "site/sections/Theme/Theme.tsx": ThemeSection,
31
+ });
32
+ ```
33
+
34
+ In `DecoPageRenderer`, check `getSyncComponent(key)` first. If found, render directly without `<Suspense>`:
35
+
36
+ ```tsx
37
+ const SyncComp = getSyncComponent(section.component);
38
+ if (SyncComp) {
39
+ return (
40
+ <section id={sectionId} data-manifest-key={section.key}>
41
+ <SyncComp {...section.props} />
42
+ </section>
43
+ );
44
+ }
45
+ // else fall back to React.lazy
46
+ ```
47
+
48
+ **Which sections to register sync**: Header, Footer, Theme, and any section visible on initial viewport (ProductInfo for PDP, SearchResult for PLP).
49
+
50
+ ## 2. Hydration Mismatch from Environment Variables
51
+
52
+ **Symptom**: Console error `A tree hydrated but some attributes of the server rendered HTML didn't match the client properties` for `__DECO_STATE`.
53
+
54
+ **Root cause**: `process.env.DECO_SITE_NAME` resolves on the server (from `.env`) but is `undefined` on the client, falling back to a different hardcoded string.
55
+
56
+ ### Fix: Vite `define` for Build-Time Injection
57
+
58
+ ```typescript
59
+ // vite.config.ts
60
+ export default defineConfig({
61
+ define: {
62
+ "process.env.DECO_SITE_NAME": JSON.stringify(
63
+ process.env.DECO_SITE_NAME || "your-site-name"
64
+ ),
65
+ },
66
+ });
67
+ ```
68
+
69
+ This replaces `process.env.DECO_SITE_NAME` at build-time in **both** SSR and client bundles, guaranteeing the same value.
70
+
71
+ **Rule**: Any `process.env.*` used in JSX that renders on both server and client needs a Vite `define` entry. Otherwise, use `import.meta.env.VITE_*` (Vite auto-exposes `VITE_`-prefixed vars to client).
72
+
73
+ ## 3. CLS from Third-Party Scripts
74
+
75
+ **Symptom**: Large Cumulative Layout Shift (CLS > 0.25) traced to external scripts injecting content.
76
+
77
+ ### Common Offenders
78
+
79
+ | Script | Problem | Fix |
80
+ |--------|---------|-----|
81
+ | Raichu/ReclameAqui `bundle.js` | Inline `<script>` loads CSS that shifts layout | Convert to React component, defer with `useEffect` + `requestIdleCallback`, add `minHeight` |
82
+ | TrustVox widget | Injects DOM after load | Add `minHeight` on container divs |
83
+ | Any analytics/chat widget | Injects floating elements | Reserve space or load after hydration |
84
+
85
+ ### Pattern: Deferred Script Component
86
+
87
+ ```tsx
88
+ function DeferredScript({ src, id, dataset, minHeight = 60 }) {
89
+ const ref = useRef<HTMLDivElement>(null);
90
+ useEffect(() => {
91
+ if (!ref.current || document.getElementById(id)) return;
92
+ const load = () => {
93
+ const script = document.createElement("script");
94
+ script.id = id;
95
+ script.async = true;
96
+ script.src = src;
97
+ Object.entries(dataset).forEach(([k, v]) => {
98
+ script.dataset[k] = v;
99
+ });
100
+ ref.current?.appendChild(script);
101
+ };
102
+ if ("requestIdleCallback" in window) {
103
+ requestIdleCallback(load, { timeout: 3000 });
104
+ } else {
105
+ setTimeout(load, 2000);
106
+ }
107
+ }, []);
108
+ return <div ref={ref} id={`${id}-container`} style={{ minHeight }} />;
109
+ }
110
+ ```
111
+
112
+ ## 4. Async Rendering Double-Flash
113
+
114
+ **Symptom**: Deferred sections show the generic gray skeleton, then replace it with the custom `LoadingFallback`, then show the real content.
115
+
116
+ **Root cause**: `DeferredSectionWrapper` renders `DefaultSectionFallback` immediately while `preloadSectionModule` fetches the module to discover if a custom `loadingFallback` exists.
117
+
118
+ ### Fix: Wait for Options Before Showing Any Fallback
119
+
120
+ ```tsx
121
+ const [optionsReady, setOptionsReady] = useState(
122
+ () => !!getSectionOptions(deferred.component),
123
+ );
124
+
125
+ useEffect(() => {
126
+ if (optionsReady) return;
127
+ preloadSectionModule(deferred.component).then((opts) => {
128
+ if (opts) setLoadedOptions(opts);
129
+ setOptionsReady(true);
130
+ });
131
+ }, [deferred.component]);
132
+
133
+ const skeleton = !optionsReady
134
+ ? null // render nothing until we know which fallback to show
135
+ : hasCustomFallback
136
+ ? createElement(loadedOptions!.loadingFallback!)
137
+ : <DefaultSectionFallback />;
138
+ ```
139
+
140
+ ## 5. Scroll-to-Top Inconsistency
141
+
142
+ **Symptom**: Clicking a product card navigates to PDP but page stays at scroll position of the shelf (near bottom).
143
+
144
+ **Root cause**: TanStack Router `scrollRestoration: true` has a known bug (#3804) where scroll-to-top doesn't always fire on forward navigation.
145
+
146
+ ### Fix: Manual Scroll-to-Top on Forward Navigation
147
+
148
+ ```typescript
149
+ // router.tsx
150
+ const router = createTanStackRouter({
151
+ routeTree,
152
+ scrollRestoration: true,
153
+ });
154
+
155
+ if (typeof window !== "undefined") {
156
+ let lastAction: string | undefined;
157
+
158
+ router.history.subscribe(({ action }) => {
159
+ lastAction = action.type;
160
+ });
161
+
162
+ router.subscribe("onResolved", (evt) => {
163
+ // PUSH/REPLACE = forward nav → scroll top
164
+ // GO = back/forward → let scrollRestoration handle it
165
+ if (evt.pathChanged && lastAction !== "GO") {
166
+ window.scrollTo({ top: 0, behavior: "smooth" });
167
+ }
168
+ });
169
+ }
170
+ ```
171
+
172
+ Use `"smooth"` for pleasant UX, `"instant"` if speed is preferred.
173
+
174
+ ## 6. Navigation Loading Feedback
175
+
176
+ **Symptom**: Clicking a product card gives no visual feedback for several seconds until the page loads.
177
+
178
+ ### Fix: ProductLink Component with Spinner Overlay
179
+
180
+ ```tsx
181
+ import { Link, useRouterState } from "@tanstack/react-router";
182
+
183
+ export default function ProductLink({ children, className, showSpinner = true, ...props }) {
184
+ const targetPath = typeof props.to === "string" ? props.to : "";
185
+ const isNavigating = useRouterState({
186
+ select: (s) => {
187
+ if (!s.isLoading || !targetPath) return false;
188
+ const pending = s.location.pathname;
189
+ const current = s.resolvedLocation?.pathname;
190
+ return pending !== current && pending === targetPath;
191
+ },
192
+ });
193
+
194
+ return (
195
+ <Link
196
+ className={`relative ${className ?? ""}${isNavigating ? " pointer-events-none" : ""}`}
197
+ {...props}
198
+ >
199
+ {children}
200
+ {showSpinner && isNavigating && (
201
+ <div className="absolute inset-0 z-20 flex items-center justify-center bg-white/60 rounded">
202
+ <span className="loading loading-spinner loading-md text-primary" />
203
+ </div>
204
+ )}
205
+ </Link>
206
+ );
207
+ }
208
+ ```
209
+
210
+ Replace `<Link>` in product card image areas with `<ProductLink>`. Use `showSpinner={false}` for text areas where the overlay would look odd.
211
+
212
+ **Complement with NavigationProgress bar** in `__root.tsx` using `useRouterState({ select: s => s.isLoading })`.
213
+
214
+ ## 7. Common React DOM Warnings
215
+
216
+ | Warning | Cause | Fix |
217
+ |---------|-------|-----|
218
+ | `fetchpriority` → `fetchPriority` | HTML attr not camelCased | Change to camelCase in JSX |
219
+ | `stroke-linecap` → `strokeLinecap` | SVG attr not camelCased | Change to camelCase in JSX |
220
+ | `fill-rule` → `fillRule` | SVG attr not camelCased | Change to camelCase in JSX |
221
+ | `clip-rule` → `clipRule` | SVG attr not camelCased | Change to camelCase in JSX |
222
+ | `class` → `className` | Preact migration leftover | Replace throughout |
223
+ | Missing `key` in list | `.map()` without `key` | Add unique `key` prop |
224
+ | `value` without `onChange` | Controlled input missing handler | Add `onChange` or use `defaultValue` |
225
+ | `selected` on `<option>` | Old HTML pattern | Use `value` on parent `<select>` |
226
+
227
+ ## 8. Performance Trace Recording (Chrome DevTools)
228
+
229
+ The Chrome Performance recorder is the most powerful tool for diagnosing CLS, hydration flash, and layout shift root causes. It captures exactly **which element shifted**, **by how many pixels**, and **what triggered it**.
230
+
231
+ ### How to Record a Trace
232
+
233
+ 1. Open Chrome DevTools → **Performance** tab
234
+ 2. Check **Web Vitals** checkbox (bottom of panel) — this enables CLS tracking
235
+ 3. Check **Screenshots** checkbox — captures visual frames to see the flash
236
+ 4. Click **Record** (circle button) or press `Ctrl+E`
237
+ 5. Reproduce the issue: F5 to reload, or navigate to the problematic page
238
+ 6. Wait for the page to fully load (3-5 seconds)
239
+ 7. Click **Stop** to end recording
240
+
241
+ ### Reading the Trace for CLS
242
+
243
+ 1. In the trace timeline, look for **red/orange diamonds** labeled "Layout Shift" in the Experience lane
244
+ 2. Click on a Layout Shift diamond — the **Summary** panel shows:
245
+ - **Score**: the CLS value for that shift (e.g., 0.59)
246
+ - **Cumulative Score**: running total
247
+ - **Elements affected**: the DOM node that moved (e.g., `DIV#ra-verified-seal`)
248
+ 3. Click the element name to jump to it in the Elements panel
249
+ 4. Look at **what happened just before** the shift in the timeline: did a script load? a stylesheet? a font?
250
+
251
+ ### Reading the Trace for Flash-of-White
252
+
253
+ 1. Enable **Screenshots** in the recording
254
+ 2. In the filmstrip at the top, look for white/blank frames between painted frames
255
+ 3. Hover over the white frame — note the timestamp
256
+ 4. At that timestamp in the Main thread, look for:
257
+ - **React reconciliation** work (`performConcurrentWorkOnRoot`)
258
+ - **Suspense fallback** activation (the `React.lazy` path)
259
+ - **Hydration** warnings (logged to console at same time)
260
+
261
+ ### Reading the Trace for Slow Navigation
262
+
263
+ 1. Record during a SPA navigation (click a product card)
264
+ 2. Look at the **Network** waterfall — find the server function call (e.g., `loadCmsPage`)
265
+ 3. Check the duration — if it's > 1s, the loader is the bottleneck
266
+ 4. Look at **Main thread** for long tasks blocking the UI after data arrives
267
+
268
+ ### Exporting and Sharing Traces
269
+
270
+ 1. After recording, click the **down arrow** (Export) in the Performance panel
271
+ 2. Save as `.json` file — this is the full trace with all timing data
272
+ 3. Share with teammates — they can **Import** it into their DevTools
273
+ 4. The trace file also works with tools like [Perfetto UI](https://ui.perfetto.dev/)
274
+
275
+ ### Quick CLS Diagnosis Shortcut
276
+
277
+ Instead of a full trace, use the **Lighthouse** panel:
278
+ 1. DevTools → **Lighthouse** → check only **Performance**
279
+ 2. Run on the specific page
280
+ 3. Scroll to **Diagnostics** → **Avoid large layout shifts**
281
+ 4. It lists the exact elements and their shift contributions
282
+
283
+ Or use the browser console:
284
+
285
+ ```javascript
286
+ new PerformanceObserver((list) => {
287
+ for (const entry of list.getEntries()) {
288
+ if (entry.hadRecentInput) continue;
289
+ console.log("CLS:", entry.value.toFixed(4), entry.sources?.map(s =>
290
+ s.node?.nodeName + "#" + s.node?.id + "." + s.node?.className
291
+ ));
292
+ }
293
+ }).observe({ type: "layout-shift", buffered: true });
294
+ ```
295
+
296
+ This logs each layout shift with the element that moved — useful for quick identification without a full trace.
297
+
298
+ ### Real Example: Diagnosing Raichu CLS
299
+
300
+ From an actual trace on espacosmart:
301
+ 1. Trace showed Layout Shift score **0.59** at 1.2s mark
302
+ 2. Affected element: `DIV#ra-verified-seal` in the Footer
303
+ 3. Just before the shift: Network showed `raichu-beta/ra-verified/bundle.js` loading
304
+ 4. The script injected CSS that resized the seal container from 0 to ~60px
305
+ 5. **Fix**: Converted inline `<script>` to deferred React component with `minHeight: 60` container
306
+
307
+ ## 9. `suppressHydrationWarning` for Dynamic Content
308
+
309
+ ### Problem
310
+
311
+ Components that render dynamic content (counters, totals, timestamps, state-dependent lists) will always differ between server and client. React throws a hydration mismatch error for every element in the subtree.
312
+
313
+ ### Fix — Target `suppressHydrationWarning` at the right level
314
+
315
+ Add `suppressHydrationWarning` to the **specific element** whose content changes, not the whole tree:
316
+
317
+ ```tsx
318
+ // BAD — suppresses warnings for the entire component
319
+ <div suppressHydrationWarning>
320
+ <header>...</header>
321
+ <ul>
322
+ {items.map(item => <li key={item.id}>{item.label}</li>)}
323
+ </ul>
324
+ </div>
325
+
326
+ // GOOD — only on the element that actually differs
327
+ <ul suppressHydrationWarning>
328
+ {items.map((item, i) => (
329
+ <li key={item.id} suppressHydrationWarning>
330
+ {item.label}
331
+ </li>
332
+ ))}
333
+ </ul>
334
+ ```
335
+
336
+ ### Common Cases
337
+
338
+ | Component | What changes | Where to add |
339
+ |-----------|-------------|--------------|
340
+ | Cart icon badge | item count | `<span>` showing count |
341
+ | User greeting | username from cookie | `<span>` with name |
342
+ | Wishlist button | favorite state | `<button>` or wrapper `<li>` |
343
+ | `UserInteractions` | wishlist/cart state per product | `<ul>` + `<li>` |
344
+
345
+ ---
346
+
347
+ ## 10. Missing `key` Props in Lists
348
+
349
+ ### Problem
350
+
351
+ React warns `Each child in a list should have a unique "key" prop` when mapping arrays to JSX without a stable key. This causes:
352
+ - Console warnings during development
353
+ - Unexpected DOM reuse leading to visual glitches
354
+
355
+ ### Fix — Always add `key` to mapped elements
356
+
357
+ ```tsx
358
+ // BAD
359
+ {products.map(product => <ProductCard {...product} />)}
360
+
361
+ // GOOD — prefer stable IDs over index
362
+ {products.map(product => (
363
+ <ProductCard key={product.productID} {...product} />
364
+ ))}
365
+
366
+ // When stable ID unavailable — index is acceptable for static lists
367
+ {items.map((item, index) => (
368
+ <li key={index}>{item.label}</li>
369
+ ))}
370
+ ```
371
+
372
+ ### `Encountered two children with the same key`
373
+
374
+ Happens when the key source has duplicates (e.g., two products with `inProductGroupWithID` pointing to the same group). Use a combination:
375
+
376
+ ```tsx
377
+ key={`${product.productID ?? ""}-${index}`}
378
+ ```
379
+
380
+ ### Common Affected Components in Deco Storefronts
381
+
382
+ | Component | Fix |
383
+ |-----------|-----|
384
+ | `BannerCarousel` dots/images | `key={index}` on each dot/image |
385
+ | `ProductShelf` product cards | `key={product.productID ?? index}` |
386
+ | `ImageGallery` images | `key={index}` |
387
+ | `SuccessFulHouse` items | `key={index}` |
388
+ | `InfoEnvironment` items | `key={item.id ?? index}` |
389
+ | `Slide01/02` big banners | `key={index}` |
390
+
391
+ ---
392
+
393
+ ## 11. Invalid HTML Nesting (`<a>` inside `<a>`)
394
+
395
+ ### Problem
396
+
397
+ Nesting an `<a>` tag inside another `<a>` is invalid HTML. React and browsers both warn, and behavior is unpredictable (inner link may be ignored or outer link may break):
398
+
399
+ ```
400
+ Warning: validateDOMNesting: <a> cannot appear as a descendant of <a>.
401
+ ```
402
+
403
+ ### Common Pattern
404
+
405
+ A "See more" link component is used inside a card that is itself a link:
406
+
407
+ ```tsx
408
+ // BAD — SeeMoreLink renders <a>, but it's inside a <Link> (which renders <a>)
409
+ <Link to={product.url}>
410
+ <img ... />
411
+ <SeeMoreLink href={product.url} /> {/* renders <a> — invalid! */}
412
+ </Link>
413
+ ```
414
+
415
+ ### Fix — `insideLink` prop to switch to `<span>`
416
+
417
+ Add an `insideLink` prop to the child component to render a non-anchor element when it's already inside a link:
418
+
419
+ ```tsx
420
+ // SeeMoreLink.tsx
421
+ interface Props {
422
+ href: string;
423
+ label?: string;
424
+ insideLink?: boolean;
425
+ }
426
+
427
+ export function SeeMoreLink({ href, label = "Ver mais", insideLink }: Props) {
428
+ if (insideLink) {
429
+ // Already inside an <a> — use span to avoid invalid nesting
430
+ return <span className="see-more-link">{label}</span>;
431
+ }
432
+ return (
433
+ <a href={href} className="see-more-link">
434
+ {label}
435
+ </a>
436
+ );
437
+ }
438
+
439
+ // Usage inside a card link:
440
+ <Link to={product.url}>
441
+ <img ... />
442
+ <SeeMoreLink href={product.url} insideLink /> {/* safe */}
443
+ </Link>
444
+ ```
445
+
446
+ ### Discovery Command
447
+
448
+ ```bash
449
+ rg '<a[^>]*>.*<a' src/ --glob '*.{tsx,ts}' -l
450
+ rg 'SeeMoreLink|VerMais' src/ --glob '*.{tsx,ts}'
451
+ ```
452
+
453
+ ---
454
+
455
+ ## 12. Diagnostic Checklist
456
+
457
+ When investigating hydration/flash issues on a Deco TanStack storefront:
458
+
459
+ 1. **Record a Performance trace** (see section 8) — look for Layout Shift diamonds and white screenshot frames
460
+ 2. **Open DevTools Console** — look for hydration mismatch errors (they tell you exactly which element differs)
461
+ 3. **Check `__DECO_STATE`** — compare server vs client values for `site.name`; if different, fix env var injection
462
+ 4. **Use the CLS console observer** (code above) — quickly identifies which elements shift without a full trace
463
+ 5. **Disable browser extensions** — some extensions inject DOM that causes hydration mismatches
464
+ 6. **Check for inline `<script>` tags in JSX** — these often load external CSS/JS that causes shifts; convert to deferred React components
465
+ 7. **Verify sync registry** — ensure all above-the-fold sections are in `registerSectionsSync`
466
+ 8. **Test with F5 (hard reload)** — SPA navigation may hide issues that appear on cold load
467
+ 9. **Check scroll behavior** — navigate from shelf to PDP; verify page scrolls to top
468
+ 10. **Compare SSR HTML vs client render** — view source (`Ctrl+U`) and compare with inspected DOM to find hydration diffs