@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,719 @@
|
|
|
1
|
+
# Migration Gotchas
|
|
2
|
+
|
|
3
|
+
45 pitfalls discovered during real migrations (espacosmart-storefront, osklen).
|
|
4
|
+
|
|
5
|
+
## 1. Section Loaders Don't Execute
|
|
6
|
+
|
|
7
|
+
Deco sections have `export const loader = async (props, req, ctx) => { ... }` that runs server-side before the component renders. In TanStack Start, these don't execute automatically. Components typed as `SectionProps<typeof loader>` expect the augmented props, but only receive the raw CMS block props.
|
|
8
|
+
|
|
9
|
+
**Symptom**: Components crash on `.find()`, `.length`, or property access of loader-provided props that are `undefined`.
|
|
10
|
+
|
|
11
|
+
**Fix**: Register them via `registerSectionLoaders()` in `setup.ts`.
|
|
12
|
+
|
|
13
|
+
**Safe-default pattern** (most pragmatic for initial migration):
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// Before: component expects loader-augmented props
|
|
17
|
+
function ProductMain({ page, productAdditional, showTogether, priceSimulation, isMobile }: SectionProps<typeof loader>) {
|
|
18
|
+
|
|
19
|
+
// After: destructure with safe defaults for all loader-only props
|
|
20
|
+
function ProductMain(rawProps: any) {
|
|
21
|
+
const {
|
|
22
|
+
page,
|
|
23
|
+
productAdditional = [], // from section loader
|
|
24
|
+
showTogether = [], // from section loader
|
|
25
|
+
showTogetherSimulation = [], // from section loader
|
|
26
|
+
priceSimulation = 0, // from section loader
|
|
27
|
+
noInterestInstallmentValue = null,
|
|
28
|
+
skuProductsKit = [], // from section loader
|
|
29
|
+
isMobile = false, // from section loader (device detection)
|
|
30
|
+
} = rawProps;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This lets the core component render while gracefully degrading features that depend on loader data (cross-selling, price simulation, etc.).
|
|
34
|
+
|
|
35
|
+
## 2. useEffect Doesn't Run on Server
|
|
36
|
+
|
|
37
|
+
Components relying on `useEffect` to populate state will render empty on SSR.
|
|
38
|
+
|
|
39
|
+
**Fix**: Use TanStack route loaders or section loaders for server-side data.
|
|
40
|
+
|
|
41
|
+
## 3. Signal .value in Render Doesn't Re-render
|
|
42
|
+
|
|
43
|
+
Reading `signal.value` inside React render doesn't create a subscription.
|
|
44
|
+
|
|
45
|
+
**Fix**: Use `useStore(signal.store)` from `@tanstack/react-store` for reactive reads.
|
|
46
|
+
|
|
47
|
+
## 4. class vs className
|
|
48
|
+
|
|
49
|
+
Preact accepts both. React only accepts `className`.
|
|
50
|
+
|
|
51
|
+
**Fix**: `grep -rn ' class=' src/ --include='*.tsx'` and replace in JSX contexts.
|
|
52
|
+
|
|
53
|
+
## 5. dangerouslySetInnerHTML Syntax
|
|
54
|
+
|
|
55
|
+
Some Deco components use `innerHTML` directly.
|
|
56
|
+
|
|
57
|
+
**Fix**: `dangerouslySetInnerHTML={{ __html: content }}`.
|
|
58
|
+
|
|
59
|
+
## 6. ComponentChildren → ReactNode
|
|
60
|
+
|
|
61
|
+
Not just a type rename. Usually fine in practice.
|
|
62
|
+
|
|
63
|
+
## 7. VTEX API Auth on Cloudflare Workers
|
|
64
|
+
|
|
65
|
+
Env vars must be set via `wrangler secret put` or `.dev.vars`, not `.env`.
|
|
66
|
+
|
|
67
|
+
## 8. Cookie Handling
|
|
68
|
+
|
|
69
|
+
In TanStack Start, manage `checkout.vtex.com__orderFormId` cookies manually via `document.cookie`.
|
|
70
|
+
|
|
71
|
+
## 9. Build Succeeds but Runtime Fails
|
|
72
|
+
|
|
73
|
+
After import rewrites, always test: build → dev → visit pages → test interactive features.
|
|
74
|
+
|
|
75
|
+
## 10. npm link for Local Dev
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
cd apps-start && npm link
|
|
79
|
+
cd ../deco-start && npm link
|
|
80
|
+
cd ../my-store && npm link @decocms/apps @decocms/start
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 11. SVG Attributes
|
|
84
|
+
|
|
85
|
+
React uses camelCase: `strokeWidth`, `fillRule`, `clipPath`, etc.
|
|
86
|
+
|
|
87
|
+
## 12. No Compat Layers
|
|
88
|
+
|
|
89
|
+
After migration: no `src/compat/`, only `~/*` alias, zero compat files in packages.
|
|
90
|
+
|
|
91
|
+
## 13. AsyncLocalStorage in Client Bundles
|
|
92
|
+
|
|
93
|
+
Use namespace import + runtime conditional (or the `deco-server-only-stubs` Vite plugin).
|
|
94
|
+
|
|
95
|
+
## 14. TanStack Start Ignores Custom Worker Entry Code
|
|
96
|
+
|
|
97
|
+
**Severity**: CRITICAL -- cache logic, admin routes, and any custom request interception will silently not work in production.
|
|
98
|
+
|
|
99
|
+
TanStack Start's Cloudflare adapter **completely ignores** the `export default` in `server.ts`. It generates its own Worker entry that calls `createStartHandler(defaultStreamHandler)` directly. Custom logic inside `createServerEntry({ async fetch(request) { ... } })` is also stripped by Vite/Rollup in production builds.
|
|
100
|
+
|
|
101
|
+
**Symptom**: Admin routes like `/live/_meta` return HTML instead of JSON. Edge caching (Cache API, X-Cache headers) doesn't work despite being implemented. Every request hits the origin at full SSR cost. The `Cache-Control` headers from route-level `headers()` functions appear correctly (because TanStack applies them), but the custom `X-Cache` header and cache storage never execute.
|
|
102
|
+
|
|
103
|
+
**Diagnosis**: Search the built `dist/server/worker-entry-*.js` bundle for your custom code (e.g., `X-Cache`, `caches.open`, `_cache/purge`). If absent, TanStack stripped it.
|
|
104
|
+
|
|
105
|
+
**Fix**: Create a **separate** `src/worker-entry.ts` file that wraps TanStack Start's built handler. Point `wrangler.jsonc` to this file instead of `@tanstack/react-start/server-entry`.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// src/worker-entry.ts
|
|
109
|
+
import "./setup";
|
|
110
|
+
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
|
|
111
|
+
import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
|
|
112
|
+
import { handleMeta, handleDecofileRead, handleDecofileReload, handleRender, corsHeaders } from "@decocms/start/admin";
|
|
113
|
+
|
|
114
|
+
const serverEntry = createServerEntry({
|
|
115
|
+
async fetch(request) {
|
|
116
|
+
return await handler.fetch(request);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export default createDecoWorkerEntry(serverEntry, {
|
|
121
|
+
admin: { handleMeta, handleDecofileRead, handleDecofileReload, handleRender, corsHeaders },
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```jsonc
|
|
126
|
+
// wrangler.jsonc -- MUST point to custom entry, NOT the default
|
|
127
|
+
{
|
|
128
|
+
"main": "./src/worker-entry.ts",
|
|
129
|
+
// NOT: "main": "@tanstack/react-start/server-entry"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This ensures admin route interception AND edge caching survive the build because they're in the Worker's own fetch handler, outside of TanStack's build pipeline.
|
|
134
|
+
|
|
135
|
+
## 15. DaisyUI v4 Theme in Preview Shell
|
|
136
|
+
|
|
137
|
+
DaisyUI v4 with Tailwind v4's `@plugin "daisyui/theme"` scopes all color variables to `[data-theme="light"]`. The admin preview HTML shell (`/live/previews/*`) must include this attribute, or colors will be wrong.
|
|
138
|
+
|
|
139
|
+
**Symptom**: Preview in admin shows default/missing colors while production looks correct.
|
|
140
|
+
|
|
141
|
+
**Fix**: Configure the preview shell in `setup.ts`:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
setRenderShell({
|
|
145
|
+
css: appCss,
|
|
146
|
+
fonts: [...],
|
|
147
|
+
theme: "light", // adds data-theme="light" to <html>
|
|
148
|
+
bodyClass: "bg-base-100 text-base-content",
|
|
149
|
+
lang: "pt-BR",
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The production HTML has `<html lang="pt-BR" data-theme="light">` set by the TanStack root layout. The preview shell must replicate this.
|
|
154
|
+
|
|
155
|
+
## 16. Admin Route Cache Bypass
|
|
156
|
+
|
|
157
|
+
`/live/` and `/.decofile` are in `DEFAULT_BYPASS_PATHS`. Admin routes are intercepted before caching.
|
|
158
|
+
|
|
159
|
+
## 17. SiteTheme is a Stub
|
|
160
|
+
|
|
161
|
+
`Theme.tsx` returns `null`. Colors come from CSS at build time, not CMS at runtime.
|
|
162
|
+
|
|
163
|
+
## 18. Loader References in JSON Schema
|
|
164
|
+
|
|
165
|
+
`Resolvable` definition with `additionalProperties: true` needed for props that accept loader refs.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 19. `import "./setup"` Ordering (CRITICAL)
|
|
170
|
+
|
|
171
|
+
`import "./setup"` MUST be the first import in both `server.ts` and `worker-entry.ts`. Without it, server functions in Vite split modules execute before `setBlocks()` has been called, causing `resolveDecoPage` to return null → 404 on client-side navigation.
|
|
172
|
+
|
|
173
|
+
**Symptom**: SSR works fine (F5), but clicking links shows "No CMS page block matches this URL".
|
|
174
|
+
|
|
175
|
+
## 20. loadDeferredSection Must Use POST
|
|
176
|
+
|
|
177
|
+
Without this, the admin shows "Incorrect type. Expected 'array'" for fields that contain loader references in the `.decofile`.
|
|
178
|
+
|
|
179
|
+
## 19. @tanstack/store subscribe() Returns Object, Not Function
|
|
180
|
+
|
|
181
|
+
**Severity: CRITICAL** -- This causes cascading failures across the entire page.
|
|
182
|
+
|
|
183
|
+
`@tanstack/store@0.9.x`'s `Store.subscribe()` returns `{ unsubscribe: Function }`, NOT a plain function. React's `useSyncExternalStore` (and `useEffect` cleanup) expect the subscribe callback to return a bare unsubscribe function. Passing the object through causes:
|
|
184
|
+
|
|
185
|
+
1. "TypeError: destroy_ is not a function" (non-minified) / "TypeError: J is not a function" (minified)
|
|
186
|
+
2. Which cascades into React #419 (hydration failure)
|
|
187
|
+
3. Which cascades into React #130 (undefined component after hydration bailout)
|
|
188
|
+
4. Which makes the entire page non-interactive (0 interactive elements)
|
|
189
|
+
|
|
190
|
+
**Symptom**: Page SSR renders fine, but client shows "J is not a function" repeating hundreds of times. All interactive elements stop working.
|
|
191
|
+
|
|
192
|
+
**Fix**: Unwrap the return value in your `Signal.subscribe()` implementation:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
subscribe(fn) {
|
|
196
|
+
const sub = store.subscribe(() => fn());
|
|
197
|
+
return typeof sub === "function" ? sub : sub.unsubscribe;
|
|
198
|
+
},
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 20. createPortal Imported from Wrong Module
|
|
202
|
+
|
|
203
|
+
In Preact, `createPortal` is available from `preact/compat` which maps to `react` in some setups. In React, `createPortal` lives in `react-dom`.
|
|
204
|
+
|
|
205
|
+
**Symptom**: `createPortal is not a function` or components using portals (modals, drawers, toasts) silently fail.
|
|
206
|
+
|
|
207
|
+
**Fix**:
|
|
208
|
+
```bash
|
|
209
|
+
# Find and replace across all files
|
|
210
|
+
grep -r 'createPortal.*from "react"' src/ --include='*.tsx' -l
|
|
211
|
+
# Change to: import { createPortal } from "react-dom";
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## 21. for Attribute Must Be htmlFor in React JSX
|
|
215
|
+
|
|
216
|
+
Preact accepts both `for` and `htmlFor` on `<label>` elements. React only accepts `htmlFor`. Using `for` causes a hydration mismatch because the server renders `for` but the client expects `htmlFor`.
|
|
217
|
+
|
|
218
|
+
**Symptom**: React #419 hydration errors on pages with labels (search bars, forms, drawers).
|
|
219
|
+
|
|
220
|
+
**Fix**: `grep -r ' for={' src/ --include='*.tsx'` and replace with `htmlFor={`.
|
|
221
|
+
|
|
222
|
+
## 22. Fresh-Specific Attributes Must Be Removed
|
|
223
|
+
|
|
224
|
+
Fresh/Preact components may use `data-fresh-disable-lock={true}` on elements. This attribute has no meaning in React and can cause hydration mismatches.
|
|
225
|
+
|
|
226
|
+
**Fix**: Remove all `data-fresh-disable-lock` attributes.
|
|
227
|
+
|
|
228
|
+
## 23. Custom useId with Math.random() Causes Hydration Mismatch
|
|
229
|
+
|
|
230
|
+
Some storefronts have a custom `useId` hook that appends `Math.random()` to generate "unique" IDs. This guarantees different IDs on server vs client, causing React #419.
|
|
231
|
+
|
|
232
|
+
**Fix**: Replace with React's native `useId`:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { useId as useReactId } from "react";
|
|
236
|
+
export const useId = useReactId;
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 24. new URL() with Relative Paths Fails in Workers
|
|
240
|
+
|
|
241
|
+
`new URL("/product/p")` works in browsers (uses `window.location` as base) but throws `Invalid URL` in Workers/Node because there's no implicit base.
|
|
242
|
+
|
|
243
|
+
**Fix**: Always provide a base URL:
|
|
244
|
+
```typescript
|
|
245
|
+
const parsed = new URL(url, "https://localhost");
|
|
246
|
+
return parsed.pathname + parsed.search;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 25. Global Variables Throw ReferenceError
|
|
250
|
+
|
|
251
|
+
Code that references undeclared globals (e.g., `userAddressData` injected by VTEX scripts) will throw `ReferenceError: X is not defined` in Workers where those scripts don't run.
|
|
252
|
+
|
|
253
|
+
**Fix**: Access via `globalThis`:
|
|
254
|
+
```typescript
|
|
255
|
+
const data = (globalThis as any).userAddressData;
|
|
256
|
+
if (data && Array.isArray(data)) { /* use data */ }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 26. Section-Type Props Use __resolveType Format
|
|
260
|
+
|
|
261
|
+
In the new `@decocms/start`, section-type props from the CMS arrive as `{ __resolveType: "site/sections/Foo.tsx", ...props }`, NOT the old `{ Component, props }` format. Components that render section props must handle this.
|
|
262
|
+
|
|
263
|
+
**Fix**: Create a `RenderSection` bridge component that:
|
|
264
|
+
1. Checks for `section.Component` (old format) and renders directly
|
|
265
|
+
2. Checks for `section.__resolveType` (new format), resolves via `getSection()` from `@decocms/start/cms`, and renders with `React.lazy` + `Suspense`
|
|
266
|
+
|
|
267
|
+
## 27. jsdom Must Be Replaced in Workers
|
|
268
|
+
|
|
269
|
+
`jsdom` is a heavy Node.js dependency that cannot run in Cloudflare Workers. Components using it for HTML sanitization must use `dompurify` instead.
|
|
270
|
+
|
|
271
|
+
**Fix**: Replace `import { JSDOM } from "jsdom"` with:
|
|
272
|
+
```typescript
|
|
273
|
+
import DOMPurify from "dompurify";
|
|
274
|
+
const clean = typeof document !== "undefined" ? DOMPurify.sanitize(html) : html;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## 28. Deno npm: Prefix Must Be Removed
|
|
278
|
+
|
|
279
|
+
Imports like `import Color from "npm:colorjs.io"` use the Deno-specific `npm:` prefix. Vite/Node don't understand it.
|
|
280
|
+
|
|
281
|
+
**Fix**: Remove the `npm:` prefix and install the package: `npm install colorjs.io`.
|
|
282
|
+
|
|
283
|
+
## 29. Device Context Must Be Server-Driven, Not Hardcoded
|
|
284
|
+
|
|
285
|
+
**Severity**: HIGH — breaks entire page layout (mobile vs desktop)
|
|
286
|
+
|
|
287
|
+
The original Deco/Fresh framework injected `ctx.device` automatically into section contexts. In the new TanStack Start stack, the `Device` context (used by `useDevice()`) must be explicitly provided with the correct value from server-side User-Agent detection.
|
|
288
|
+
|
|
289
|
+
**Symptom**: All visitors see the mobile layout regardless of device. The `useDevice()` hook always returns `{ isMobile: true }` because the `Device.Provider` was hardcoded with `value={{ isMobile: true }}` in `__root.tsx`.
|
|
290
|
+
|
|
291
|
+
**Root cause**: The root route can't use `createServerFn` for device detection (causes Rollup code-split errors with `tss-serverfn-split`). And the Device context default was set to mobile.
|
|
292
|
+
|
|
293
|
+
**Fix**: Detect device inside each page route's existing `createServerFn` loader (which already has access to `getRequestHeader("user-agent")`), return `isMobile` alongside the page data, and wrap the page component with `<Device.Provider>`:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// In routes/index.tsx or routes/$.tsx
|
|
297
|
+
const MOBILE_RE = /mobile|android|iphone|ipad|ipod|webos|blackberry|opera mini|iemobile/i;
|
|
298
|
+
|
|
299
|
+
const loadPage = createServerFn({ method: "GET" }).handler(async () => {
|
|
300
|
+
const ua = getRequestHeader("user-agent") ?? "";
|
|
301
|
+
const matcherCtx = { userAgent: ua, url: getRequestUrl().toString(), path: "/", cookies: getCookies() };
|
|
302
|
+
const page = await resolveDecoPage("/", matcherCtx);
|
|
303
|
+
return { page, isMobile: MOBILE_RE.test(ua) };
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
function HomePage() {
|
|
307
|
+
const { page, isMobile } = Route.useLoaderData();
|
|
308
|
+
return (
|
|
309
|
+
<Device.Provider value={{ isMobile }}>
|
|
310
|
+
<DecoPageRenderer sections={page.resolvedSections} />
|
|
311
|
+
</Device.Provider>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Remove the hardcoded `<Device.Provider value={{ isMobile: true }}>` from `__root.tsx`.
|
|
317
|
+
|
|
318
|
+
**Key constraint**: Do NOT put `createServerFn` in `__root.tsx` — TanStack Start's server function splitter cannot handle it there.
|
|
319
|
+
|
|
320
|
+
## 30. Stale Edge Cache After Deploy Requires Explicit Purge
|
|
321
|
+
|
|
322
|
+
**Severity**: MEDIUM — causes "Failed to fetch dynamically imported module" errors
|
|
323
|
+
|
|
324
|
+
After deploying a new build to Cloudflare Workers, the edge cache may still serve old HTML that references previous JS bundle hashes. This causes module import failures.
|
|
325
|
+
|
|
326
|
+
**Fix**: After every deploy, purge the cache:
|
|
327
|
+
1. Set a `PURGE_TOKEN` secret: `npx wrangler secret put PURGE_TOKEN`
|
|
328
|
+
2. Call the purge endpoint: `POST /_cache/purge` with `Authorization: Bearer <token>` and body `{"paths":["/"]}`
|
|
329
|
+
3. Automate this in CI/CD (see the deploy.yml workflow)
|
|
330
|
+
|
|
331
|
+
## 31. CSS Theme Class Prefixes Must Not Be Renamed
|
|
332
|
+
|
|
333
|
+
**Severity**: HIGH — breaks all theme colors
|
|
334
|
+
|
|
335
|
+
The original site uses `seasonal-*` CSS class prefixes for theme variables (e.g., `bg-seasonal-brand-terciary-1`, `text-seasonal-neutral-1`). During migration, do NOT rename these to `header-*`, `footer-*`, or any other prefix. The theme variables are defined centrally and all components reference the same `seasonal-*` namespace.
|
|
336
|
+
|
|
337
|
+
**Fix**: Only change what React strictly requires: `class` → `className`, `for` → `htmlFor`. Preserve all original CSS class names exactly.
|
|
338
|
+
|
|
339
|
+
## 32. Section Loader Logic Must Not Be Stripped
|
|
340
|
+
|
|
341
|
+
**Severity**: HIGH — sections render empty/broken
|
|
342
|
+
|
|
343
|
+
During migration, section loaders (e.g., `sections/Header/Header.tsx`) may have their async data-fetching logic removed. For example, the `ctx.invoke.vtex.loaders.categories.tree()` call that populates navigation menus. Without it, the header renders with no category links.
|
|
344
|
+
|
|
345
|
+
**Fix**: Keep all section loader logic intact. The loader signature `(props, req, ctx) => {...}` and the `ctx.invoke` calls should be preserved as-is.
|
|
346
|
+
|
|
347
|
+
## 33. usePartialSection is a No-Op
|
|
348
|
+
|
|
349
|
+
**Severity**: HIGH — tab switching, filter toggling, any partial section re-render breaks silently
|
|
350
|
+
|
|
351
|
+
Deco's `usePartialSection` hook re-renders a section with new props by making a server request for just that section's HTML. In the React port, this mechanism doesn't exist — `usePartialSection` returns empty data attributes.
|
|
352
|
+
|
|
353
|
+
**Symptom**: Tabbed product shelves only show the first tab. Clicking other tabs does nothing. Filter visibility toggles don't work. Any component using `usePartialSection` for dynamic props appears frozen.
|
|
354
|
+
|
|
355
|
+
**Fix**: Replace with React `useState` for client-side state switching. If all tab data is already loaded server-side (common for tabbed shelves where the loader fetches all tabs), just switch between the data client-side:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Before: relies on usePartialSection to re-render with new tabIndex
|
|
359
|
+
<button {...usePartialSection({ props: { tabIndex: i } })}>
|
|
360
|
+
|
|
361
|
+
// After: React state-driven tab switching
|
|
362
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
363
|
+
<button onClick={() => setActiveTab(i)}>
|
|
364
|
+
// Then render tabs[activeTab].products instead of tabs[tabIndex].products
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
For filter toggles, replace `usePartialSection({ props: { openFilter: !openFilter } })` with `useState`:
|
|
368
|
+
```typescript
|
|
369
|
+
const [openFilter, setOpenFilter] = useState(true);
|
|
370
|
+
<button onClick={() => setOpenFilter(!openFilter)}>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## 34. Commerce Loaders Are Blind to the URL
|
|
374
|
+
|
|
375
|
+
**Severity**: CRITICAL — search and category pages return wrong/no products
|
|
376
|
+
|
|
377
|
+
When `resolve.ts` processes CMS blocks, it passes only the static CMS block props to commerce loaders (PLP, PDP). The current URL, query string (`?q=`), path (`/drywall`), sort, pagination, and filter parameters are never forwarded.
|
|
378
|
+
|
|
379
|
+
**Symptom**: Search pages (`/s?q=parafuso`) return zero products. Category pages (`/drywall`) show random/no products. Sort and pagination controls do nothing.
|
|
380
|
+
|
|
381
|
+
**Root cause**: `resolveValue()` in `resolve.ts` calls commerce loaders with `resolvedProps` (CMS block config only). The `matcherCtx` (containing URL, path, user-agent) is used for matcher evaluation but never passed to commerce loaders.
|
|
382
|
+
|
|
383
|
+
**Fix**: Pass `matcherCtx` as a second argument to commerce loaders in `resolve.ts`. Then the PLP loader can extract `?q=` for search, path for categories, `?sort=` for sorting, `?page=` for pagination, and `?filter.X=Y` for facets.
|
|
384
|
+
|
|
385
|
+
This is a change in `@decocms/start` (resolve.ts). Until upstreamed, use patch-package or vendor the file.
|
|
386
|
+
|
|
387
|
+
## 35. VTEX Product Loaders Ship with Empty priceSpecification
|
|
388
|
+
|
|
389
|
+
**Severity**: HIGH — no discount badges, no strikethrough prices, no installments
|
|
390
|
+
|
|
391
|
+
All three VTEX product loaders (`vtexProductList`, `productListingPage`, `productDetailsPage`) build offers with `priceSpecification: []`. The `useOffer()` hook depends on this array to extract `ListPrice` (for discount math + strikethrough), `SalePrice`, and `Installment` entries.
|
|
392
|
+
|
|
393
|
+
**Symptom**: Product cards show only one price (no strikethrough). No "X% OFF" discount badge. No "Ou em Nx de R$ X sem juros" installment text.
|
|
394
|
+
|
|
395
|
+
**Fix**: Add a `buildPriceSpecification()` helper to each loader that transforms the VTEX `commertialOffer` data:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
function buildPriceSpecification(offer: any): any[] {
|
|
399
|
+
const specs: any[] = [];
|
|
400
|
+
if (offer.ListPrice != null) {
|
|
401
|
+
specs.push({ "@type": "UnitPriceSpecification", priceType: "https://schema.org/ListPrice", price: offer.ListPrice });
|
|
402
|
+
}
|
|
403
|
+
if (offer.Price != null) {
|
|
404
|
+
specs.push({ "@type": "UnitPriceSpecification", priceType: "https://schema.org/SalePrice", price: offer.Price });
|
|
405
|
+
}
|
|
406
|
+
// Find best no-interest installment
|
|
407
|
+
const noInterest = (offer.Installments ?? [])
|
|
408
|
+
.filter((i: any) => i.InterestRate === 0)
|
|
409
|
+
.sort((a: any, b: any) => b.NumberOfInstallments - a.NumberOfInstallments);
|
|
410
|
+
if (noInterest.length > 0) {
|
|
411
|
+
const best = noInterest[0];
|
|
412
|
+
specs.push({
|
|
413
|
+
"@type": "UnitPriceSpecification",
|
|
414
|
+
priceType: "https://schema.org/SalePrice",
|
|
415
|
+
priceComponentType: "https://schema.org/Installment",
|
|
416
|
+
billingDuration: best.NumberOfInstallments,
|
|
417
|
+
billingIncrement: best.Value,
|
|
418
|
+
price: best.TotalValuePlusInterestRate,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return specs;
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
This is a change in `@decocms/apps`. Until upstreamed, patch or vendor the loader files.
|
|
426
|
+
|
|
427
|
+
## 36. VTEX Facets API Response Structure Mismatch
|
|
428
|
+
|
|
429
|
+
The VTEX Intelligent Search facets endpoint returns `{ facets: ISFacetGroup[] }`, NOT a direct `ISFacetGroup[]` array. Accessing `response` directly as an array yields no filter data.
|
|
430
|
+
|
|
431
|
+
Additionally, `PRICERANGE` facets must be converted to `FilterToggle` format (with `value: "min:max"` strings) for the existing `Filters.tsx` component to render them. The component's `isToggle()` filter drops anything that isn't `FilterToggle`.
|
|
432
|
+
|
|
433
|
+
**Fix**: Unwrap with `const facetGroups = response.facets ?? [];` and convert price ranges:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
if (group.type === "PRICERANGE") {
|
|
437
|
+
return { "@type": "FilterToggle" as const, key: "price", label: group.name, quantity: 0,
|
|
438
|
+
values: group.values.map((v) => ({
|
|
439
|
+
value: `${v.range.from}:${v.range.to}`, label: `R$ ${v.range.from} - R$ ${v.range.to}`,
|
|
440
|
+
quantity: v.quantity, selected: false, url: `?filter.price=${v.range.from}:${v.range.to}`,
|
|
441
|
+
})),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## 37. DaisyUI v4 Collapse Broken with Tailwind v4
|
|
447
|
+
|
|
448
|
+
**Severity**: MEDIUM — filter sidebars, FAQ accordions, any collapsible section renders collapsed
|
|
449
|
+
|
|
450
|
+
DaisyUI v4's collapse component uses `grid-template-rows: auto 0fr` with `content-visibility: hidden` and expands via `:has(>input:checked)`. In combination with Tailwind v4, the expand chain breaks — content stays collapsed regardless of checkbox state.
|
|
451
|
+
|
|
452
|
+
**Symptom**: Filter sidebar shows as empty space. Collapse titles may render but content is permanently hidden. Custom CSS overrides on `.collapse` conflict with DaisyUI's generated styles.
|
|
453
|
+
|
|
454
|
+
**Fix**: Replace DaisyUI collapse with native `<details>/<summary>` HTML elements:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// Before: DaisyUI collapse with hidden checkbox
|
|
458
|
+
<div className="collapse">
|
|
459
|
+
<input type="checkbox" defaultChecked />
|
|
460
|
+
<div className="collapse-title">Category</div>
|
|
461
|
+
<div className="collapse-content">...filters...</div>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
// After: Native HTML, works everywhere
|
|
465
|
+
<details open className="group">
|
|
466
|
+
<summary className="cursor-pointer font-semibold">Category</summary>
|
|
467
|
+
<div className="mt-2">...filters...</div>
|
|
468
|
+
</details>
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## 38. Signal Shim Doesn't Auto-Trigger React Re-renders
|
|
472
|
+
|
|
473
|
+
**Severity**: HIGH — drawers don't open, cart badge doesn't update, any signal-driven UI appears frozen
|
|
474
|
+
|
|
475
|
+
The Preact-to-React signal compat shim has a pub/sub pattern (`_listeners`), but reading `signal.value` in a React render function creates NO subscription. React components don't re-render when the signal changes.
|
|
476
|
+
|
|
477
|
+
**Symptom**: Setting `displayCart.value = true` doesn't open the cart drawer. Cart item count badge stays at 0 after adding items. Menu drawer toggle does nothing.
|
|
478
|
+
|
|
479
|
+
**Root cause**: In Preact, `@preact/signals` automatically tracks signal reads in render and re-renders. The shim just has get/set on `.value` with manual `_listeners` — React has no awareness of it.
|
|
480
|
+
|
|
481
|
+
**Fix (recommended)**: Use `useStore` from `@tanstack/react-store` for components that need reactive reads:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { useStore } from "@tanstack/react-store";
|
|
485
|
+
const { displayCart } = useUI();
|
|
486
|
+
const open = useStore(displayCart.store); // auto re-renders on change
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Fix (interim)**: For components not yet migrated to `useStore`, bridge with `useState` + `useEffect`:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
const { displayCart } = useUI();
|
|
493
|
+
const [open, setOpen] = useState(displayCart.value);
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
const unsub = displayCart.subscribe(() => setOpen(displayCart.value));
|
|
496
|
+
return unsub;
|
|
497
|
+
}, []);
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Fix (DaisyUI drawers)**: Since DaisyUI drawers are checkbox-driven, directly toggle the DOM checkbox as a pragmatic workaround:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
const toggleDrawer = (id: string, open: boolean) => {
|
|
504
|
+
const checkbox = document.getElementById(id) as HTMLInputElement;
|
|
505
|
+
if (checkbox) checkbox.checked = open;
|
|
506
|
+
};
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## 39. Cart Requires Server-Side Proxy for VTEX API (CORS)
|
|
510
|
+
|
|
511
|
+
**Severity**: HIGH — add-to-cart, minicart, and checkout flow completely broken
|
|
512
|
+
|
|
513
|
+
The storefront domain (e.g., `espacosmart-tanstack.deco.site`) differs from the VTEX checkout domain (`lojaespacosmart.vtexcommercestable.com.br`). Direct browser `fetch()` calls to VTEX are blocked by CORS. Additionally, the `checkout.vtex.com__orderFormId` cookie is scoped to the VTEX domain and inaccessible from the storefront.
|
|
514
|
+
|
|
515
|
+
**Fix**: Use TanStack Start `createServerFn` to create server-side proxy functions:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// src/lib/vtex-cart-server.ts
|
|
519
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
520
|
+
|
|
521
|
+
export const getOrCreateCart = createServerFn({ method: "GET" })
|
|
522
|
+
.validator((orderFormId: string) => orderFormId)
|
|
523
|
+
.handler(async ({ data: orderFormId }) => {
|
|
524
|
+
const url = orderFormId
|
|
525
|
+
? `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${orderFormId}`
|
|
526
|
+
: `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm`;
|
|
527
|
+
const res = await fetch(url, {
|
|
528
|
+
method: "POST",
|
|
529
|
+
headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
|
|
530
|
+
body: JSON.stringify({ expectedOrderFormSections: ["items", "totalizers", "shippingData", "clientPreferencesData", "storePreferencesData", "marketingData"] }),
|
|
531
|
+
});
|
|
532
|
+
return res.json();
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
The `useCart` hook manages the `orderFormId` in a client-side cookie and calls these server functions.
|
|
537
|
+
|
|
538
|
+
**Checkout URL**: The minicart's "Finalizar Compra" link must append the `orderFormId` as a query parameter since the VTEX checkout domain can't read the storefront's cookies:
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## 40. Filter Sidebar Invisible Due to Background Color Match
|
|
545
|
+
|
|
546
|
+
**Severity**: LOW — cosmetic, but confusing during development
|
|
547
|
+
|
|
548
|
+
The aside element for search/category filters renders correctly in the DOM (proper width, height, content) but appears invisible because its background matches the page background (e.g., both `#E9E9E9`).
|
|
549
|
+
|
|
550
|
+
**Symptom**: Filters appear "non-existent" even though they're in the DOM. Filter links are accessible but invisible.
|
|
551
|
+
|
|
552
|
+
**Fix**: Add a contrasting background to the filter aside:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
<aside className="... bg-white rounded-lg p-4">
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## 41. Component Props `class` vs `className` Causes Silent Failures
|
|
559
|
+
|
|
560
|
+
**Severity**: HIGH — specific component features silently disappear
|
|
561
|
+
|
|
562
|
+
Gotcha #4 covers JSX attributes (`class=` on HTML elements), but this is about **component props** that are destructured as `class`. Preact components often destructure `{ class: _class }` from props because Preact accepts both `class` and `className`. In React, only `className` is passed, so `_class` ends up as `undefined`.
|
|
563
|
+
|
|
564
|
+
**Symptom**: The Drawer component's `className="drawer-end"` never reaches the rendered div. CartDrawer renders without `drawer-end`, making it overlay the wrong side or not render at all.
|
|
565
|
+
|
|
566
|
+
**Fix**: In component interfaces, accept both and merge:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// Before (Preact-style):
|
|
570
|
+
function Drawer({ class: _class, ...rest }) {
|
|
571
|
+
return <div className={`drawer ${_class}`}>
|
|
572
|
+
|
|
573
|
+
// After (React-compatible):
|
|
574
|
+
function Drawer({ className, ...rest }) {
|
|
575
|
+
return <div className={`drawer ${className ?? ""}`}>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Search for `class:` in component destructuring patterns across all files, not just in JSX attributes.
|
|
579
|
+
|
|
580
|
+
## 42. Tailwind v4 Logical vs Physical Property Cascade Conflict
|
|
581
|
+
|
|
582
|
+
**Severity**: CRITICAL — causes container width mismatches across the entire site
|
|
583
|
+
|
|
584
|
+
Tailwind v4 generates **logical CSS properties** (`padding-inline`, `margin-inline`) while Tailwind v3 generated **physical properties** (`padding-left`, `padding-right`). When an element has BOTH shorthand (`px-*`) and longhand (`pl-*`/`pr-*`) responsive classes, the cascade breaks silently.
|
|
585
|
+
|
|
586
|
+
**Symptom**: Containers are narrower or have asymmetric padding compared to production. The layout "looks off" at certain breakpoints but works at others.
|
|
587
|
+
|
|
588
|
+
**Root cause**: In Tailwind v3, `md:px-6` and `sm:pl-0` both target `padding-left` — same CSS property, media query specificity decides the winner. In Tailwind v4, `md:px-6` targets `padding-inline` (shorthand) while `sm:pl-0` targets `padding-inline-start` (longhand). These are different CSS properties. If `padding-inline-start` appears later in the compiled stylesheet, it overrides the shorthand's start value, creating asymmetric padding.
|
|
589
|
+
|
|
590
|
+
**Example**:
|
|
591
|
+
```html
|
|
592
|
+
<!-- This pattern exists in many Deco storefronts -->
|
|
593
|
+
<div class="pl-4 sm:pl-0 md:px-6 xl-b:px-0 max-w-[1280px] mx-auto">
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
In Tailwind v3: at `md` viewport, `px-6` sets `padding-left: 1.5rem` and `padding-right: 1.5rem`, cleanly overriding `sm:pl-0`.
|
|
597
|
+
|
|
598
|
+
In Tailwind v4: at `md` viewport, `px-6` sets `padding-inline: 1.5rem`, but `pl-0` (from `sm:`) may still override `padding-inline-start` depending on stylesheet order.
|
|
599
|
+
|
|
600
|
+
**Fix**: Replace mixed shorthand + longhand patterns with consistent longhand properties:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
md:px-6 xl-b:px-0 → md:pl-6 md:pr-6 xl-b:pl-0 xl-b:pr-0
|
|
604
|
+
px-4 lg:px-6 xl-b:px-0 → pl-4 pr-4 lg:pl-6 lg:pr-6 xl-b:pl-0 xl-b:pr-0
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Detection**: Find all elements with mixed patterns:
|
|
608
|
+
```bash
|
|
609
|
+
grep -rn 'px-[0-9].*pl-\|pl-.*px-[0-9]\|px-[0-9].*pr-\|pr-.*px-[0-9]' src/ --include='*.tsx'
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Only convert `px-*` on elements that ALSO have `pl-*` or `pr-*`. Don't blindly replace all `px-*` across the codebase — elements with only `px-*` (no mixed longhand) work fine.
|
|
613
|
+
|
|
614
|
+
Also check for the same issue with `mx-*` mixed with `ml-*`/`mr-*`, and `my-*` mixed with `mt-*`/`mb-*`.
|
|
615
|
+
|
|
616
|
+
## 43. CSS oklch() Color Variables Must Store Triplets, Not Hex
|
|
617
|
+
|
|
618
|
+
**Severity**: HIGH — all SVG icons render as black, brand colors break
|
|
619
|
+
|
|
620
|
+
Sites that use `oklch(var(--variable))` in SVG fill/stroke attributes (common in Deco storefronts with seasonal/theme color systems) require the CSS variables to store **oklch triplets** (`100% 0.00 0deg`), NOT hex values (`#FFF`). `oklch(#FFF)` is invalid CSS — the browser ignores it and falls back to black.
|
|
621
|
+
|
|
622
|
+
**Symptom**: Slider arrows, footer icons, search icons, filter icons — anything using `oklch(var(--...))` — renders as black circles/shapes instead of the brand colors.
|
|
623
|
+
|
|
624
|
+
**Root cause**: The original site's Theme section (via Deco CMS) outputs oklch triplets into CSS variables. During migration, if the CSS variables are manually set to hex values, every `oklch()` wrapper produces invalid CSS.
|
|
625
|
+
|
|
626
|
+
**Fix**: Convert all theme CSS variables from hex to oklch triplets:
|
|
627
|
+
```css
|
|
628
|
+
/* WRONG — invalid CSS when used as oklch(var(--bg-seasonal-2)) */
|
|
629
|
+
--bg-seasonal-2: #FFF;
|
|
630
|
+
|
|
631
|
+
/* CORRECT — oklch(100% 0.00 0deg) is valid */
|
|
632
|
+
--bg-seasonal-2: 100% 0.00 0deg;
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Dual-usage caveat**: Variables used BOTH inside `oklch()` wrappers AND directly in CSS properties need different handling:
|
|
636
|
+
|
|
637
|
+
```css
|
|
638
|
+
/* @theme entries for Tailwind utilities — need oklch() wrapper */
|
|
639
|
+
--color-bg-seasonal-1: oklch(var(--bg-seasonal-1));
|
|
640
|
+
|
|
641
|
+
/* Direct CSS usage — also needs oklch() wrapper */
|
|
642
|
+
background-color: oklch(var(--bg-seasonal-1));
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
The DaisyUI v4 pattern: `@theme` entries map `--color-X` to `var(--Y)`. Tailwind generates `background-color: var(--color-X)` which resolves to the raw triplet — invalid without the `oklch()` wrapper. Wrap all `@theme` entries that reference oklch-triplet variables.
|
|
646
|
+
|
|
647
|
+
**Python conversion helper**:
|
|
648
|
+
```python
|
|
649
|
+
from colorjs import Color
|
|
650
|
+
c = Color("#EE4F31")
|
|
651
|
+
l, c_val, h = c.convert("oklch").coords()
|
|
652
|
+
print(f"{l*100:.2f}% {c_val:.2f} {h:.0f}deg") # 64.42% 0.20 33deg
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## 44. Runtime Module Import Kills Lazy-Loaded Sections
|
|
656
|
+
|
|
657
|
+
**Severity**: HIGH — sections silently disappear, data appears in RSC streaming but component renders nothing
|
|
658
|
+
|
|
659
|
+
Vite tree-shakes unused imports in production builds, so a section file that imports a non-existent module may pass `npm run build` without errors. But at runtime, when the section is dynamically imported via `registerSections`'s lazy `() => import("./sections/X")`, ALL imports in the module execute. A missing file kills the entire section module.
|
|
660
|
+
|
|
661
|
+
**Symptom**: Product shelves or other sections disappear. HTML size drops significantly. Product data appears in React streaming data (`$R[...]` notation) but zero product cards render as actual HTML. No error in the build log.
|
|
662
|
+
|
|
663
|
+
**Example**:
|
|
664
|
+
```typescript
|
|
665
|
+
// sections/Product/ProductShelf.tsx
|
|
666
|
+
import LoadingCard from "~/components/product/loadingCard"; // file doesn't exist!
|
|
667
|
+
export { default, loader } from "~/components/product/ProductShelf";
|
|
668
|
+
|
|
669
|
+
export function LoadingFallback() {
|
|
670
|
+
return <LoadingCard />; // only used here — tree-shaken in build
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Build passes because `LoadingFallback` is a named export that nothing imports. But at runtime, the dynamic `import("./sections/Product/ProductShelf")` executes the module, hits the missing `loadingCard` import, and the entire section fails to load.
|
|
675
|
+
|
|
676
|
+
**Fix**: Create the missing file, even if it's a minimal stub:
|
|
677
|
+
```typescript
|
|
678
|
+
// components/product/loadingCard.tsx
|
|
679
|
+
export default function LoadingCard() {
|
|
680
|
+
return <div className="animate-pulse bg-base-200 h-[400px] w-[200px] rounded" />;
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**Prevention**: After copying files from the original repo, verify all imports resolve:
|
|
685
|
+
```bash
|
|
686
|
+
npx tsc --noEmit # catches missing modules that Vite's tree-shaking hides
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
## 45. GitHub Packages npm Requires Auth Even for Public Packages
|
|
690
|
+
|
|
691
|
+
**Severity**: MEDIUM — blocks dependency installation for new contributors and CI
|
|
692
|
+
|
|
693
|
+
GitHub Packages' npm registry (`npm.pkg.github.com`) requires authentication even for public packages. This is a known limitation that GitHub has not resolved. Attempting to `npm install` a public `@decocms/*` package without a token returns `401 Unauthorized`.
|
|
694
|
+
|
|
695
|
+
**Workaround A (recommended for development)**: Use `github:` Git URL syntax instead of npm registry references. This bypasses the npm registry entirely and uses Git HTTPS (no auth needed for public repos):
|
|
696
|
+
|
|
697
|
+
```json
|
|
698
|
+
{
|
|
699
|
+
"@decocms/apps": "github:decocms/apps-start",
|
|
700
|
+
"@decocms/start": "github:decocms/deco-start#main"
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**Important**: The repo name in the `github:` URL must match the actual GitHub repo name, not the npm package name. `@decocms/start` is published from repo `decocms/deco-start`, NOT `decocms/start`.
|
|
705
|
+
|
|
706
|
+
**Workaround B (recommended for production)**: Publish to npmjs.com instead. Only npm's public registry supports truly zero-auth public package installation.
|
|
707
|
+
|
|
708
|
+
**Workaround C (if you must use GitHub Packages)**: Generate a GitHub PAT with `read:packages` scope and configure:
|
|
709
|
+
```bash
|
|
710
|
+
npm config set //npm.pkg.github.com/:_authToken <YOUR_TOKEN>
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Or in project `.npmrc` with an env var (for CI):
|
|
714
|
+
```
|
|
715
|
+
@decocms:registry=https://npm.pkg.github.com
|
|
716
|
+
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Tradeoff with `github:` syntax**: No semver resolution — `npm update` is meaningless. Pin to a tag for stability: `github:decocms/deco-start#v0.14.2`. Without a tag, you get HEAD of the default branch.
|