@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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- 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
|