@decocms/start 0.38.0 → 0.40.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 (41) hide show
  1. package/.agents/skills/deco-migrate-script/SKILL.md +434 -0
  2. package/.agents/skills/deco-to-tanstack-migration/SKILL.md +382 -0
  3. package/.agents/skills/deco-to-tanstack-migration/references/admin-cms.md +154 -0
  4. package/{.cursor/skills/deco-async-rendering-site-guide/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/async-rendering.md} +296 -31
  5. package/.agents/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  6. package/.agents/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  7. package/.agents/skills/deco-to-tanstack-migration/references/css-styling.md +156 -0
  8. package/.agents/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  9. package/.agents/skills/deco-to-tanstack-migration/references/gotchas.md +13 -0
  10. package/{.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/hydration-fixes.md} +139 -4
  11. package/.agents/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  12. package/{.cursor/skills/deco-islands-migration/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/islands.md} +0 -14
  13. package/.agents/skills/deco-to-tanstack-migration/references/jsx-migration.md +80 -0
  14. package/.agents/skills/deco-to-tanstack-migration/references/matchers.md +1064 -0
  15. package/{.cursor/skills/deco-tanstack-navigation/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/navigation.md} +1 -16
  16. package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  17. package/.agents/skills/deco-to-tanstack-migration/references/react-hooks-patterns.md +142 -0
  18. package/.agents/skills/deco-to-tanstack-migration/references/react-signals-state.md +72 -0
  19. package/{.cursor/skills/deco-tanstack-search/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/search.md} +1 -13
  20. package/.agents/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  21. package/{.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/storefront-patterns.md} +1 -137
  22. package/.agents/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  23. package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +165 -0
  24. package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +209 -0
  25. package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  26. package/.agents/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  27. package/.agents/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  28. package/.agents/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  29. package/.agents/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  30. package/.agents/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  31. package/README.md +45 -0
  32. package/package.json +2 -1
  33. package/src/admin/index.ts +2 -0
  34. package/src/admin/invoke.ts +53 -5
  35. package/src/admin/setup.ts +7 -1
  36. package/src/apps/autoconfig.ts +50 -72
  37. package/src/sdk/invoke.ts +123 -12
  38. package/src/sdk/requestContext.ts +42 -0
  39. package/src/sdk/setupApps.ts +211 -0
  40. package/src/sdk/workerEntry.ts +6 -0
  41. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +0 -270
@@ -0,0 +1,382 @@
1
+ ---
2
+ name: deco-to-tanstack-migration
3
+ description: Consolidated migration skill for Deco storefronts. Phase-based playbook for Fresh/Preact/Deno to TanStack Start/React/Cloudflare Workers. Covers all phases from scaffold to async rendering, plus post-migration patterns, hydration fixes, navigation, search, matchers, and islands elimination. Single entry point — all deep-dive content in references/.
4
+ ---
5
+
6
+ # Deco-to-TanStack-Start Migration Playbook
7
+
8
+ Phase-based playbook for converting `deco-sites/*` storefronts from Fresh/Preact/Deno to TanStack Start/React/Cloudflare Workers. Battle-tested on espacosmart-storefront (100+ sections, VTEX, async rendering).
9
+
10
+ ## Architecture Boundaries
11
+
12
+ | Layer | npm Package | Purpose | Must NOT Contain |
13
+ |-------|-------------|---------|-----------------|
14
+ | **@decocms/start** | `@decocms/start` | CMS resolution, DecoPageRenderer, worker entry, sdk (useScript, signal, clx) | Preact shims, widget types, site-specific maps |
15
+ | **@decocms/apps** | `@decocms/apps` | VTEX/Shopify loaders, commerce types, commerce sdk (useOffer, formatPrice, analytics) | Passthrough HTML components, Preact/Fresh refs |
16
+ | **Site repo** | (not published) | All UI: components, hooks, types, routes, styles | No compat/ layer, no aliases beyond `~` |
17
+
18
+ ### Architecture Map
19
+
20
+ | Old Stack | New Stack |
21
+ |-----------|-----------|
22
+ | Deno + Fresh | Node + TanStack Start |
23
+ | Preact + Islands | React 19 + React Compiler |
24
+ | @preact/signals | @tanstack/store + @tanstack/react-store |
25
+ | Deco CMS runtime | Static JSON blocks via @decocms/start |
26
+ | $fresh/runtime.ts | Inlined (asset() removed, IS_BROWSER inlined) |
27
+ | @deco/deco/* | @decocms/start/sdk/* or inline stubs |
28
+ | apps/commerce/types | @decocms/apps/commerce/types |
29
+ | apps/website/components/* | ~/components/ui/* (local React) |
30
+ | apps/{platform}/hooks/* | ~/hooks/useCart (real implementation) |
31
+ | ~/sdk/useOffer | @decocms/apps/commerce/sdk/useOffer |
32
+ | ~/sdk/useScript | @decocms/start/sdk/useScript |
33
+ | ~/sdk/signal | @decocms/start/sdk/signal |
34
+
35
+ ## Migration Phases
36
+
37
+ Each phase has entry/exit criteria. Follow in order. Automation % indicates how much can be done with bulk sed/grep.
38
+
39
+ | Phase | Name | Automation | Reference |
40
+ |-------|------|-----------|-----------|
41
+ | [0](#phase-0--scaffold) | Scaffold & Copy | 100% | `templates/` |
42
+ | [1](#phase-1--imports--jsx) | Import Rewrites | ~90% | `references/imports/` |
43
+ | [2](#phase-2--signals--state) | Signals & State | ~50% | `references/signals/` |
44
+ | [3](#phase-3--deco-framework) | Deco Framework Elimination | ~80% | `references/deco-framework/` |
45
+ | [4](#phase-4--commerce--types) | Commerce Types & UI | ~70% | `references/commerce/` |
46
+ | [5](#phase-5--platform-hooks) | Platform Hooks | 0% | `references/platform-hooks/` |
47
+ | [6](#phase-6--islands-elimination) | Islands Elimination | ~60% | `references/islands.md` |
48
+ | [7](#phase-7--section-registry) | Section Registry & Setup | 0% | `references/async-rendering.md` |
49
+ | [8](#phase-8--routes--cms) | Routes & CMS | template | `references/navigation.md` |
50
+ | [9](#phase-9--worker-entry) | Worker Entry & Server | template | `references/worker-cloudflare.md` |
51
+ | [10](#phase-10--matchers) | Matchers | ~40% | `references/matchers.md` |
52
+ | [11](#phase-11--async-rendering) | Async Rendering & Polish | 0% | `references/async-rendering.md` |
53
+ | [12](#phase-12--search) | Search | 0% | `references/search.md` |
54
+
55
+ ---
56
+
57
+ ### Phase 0 — Scaffold
58
+
59
+ **Entry**: Source site accessible, @decocms/start + @decocms/apps published
60
+
61
+ **Actions**:
62
+ 1. Create TanStack Start project
63
+ 2. Copy `src/components/`, `src/sections/`, `src/islands/`, `src/hooks/`, `src/sdk/`, `src/loaders/` from source
64
+ 3. Copy `.deco/blocks/` (CMS content)
65
+ 4. Copy `static/` assets
66
+ 5. Create `package.json` — see `templates/package-json.md`
67
+ 6. Create `vite.config.ts` — see `templates/vite-config.md`
68
+ 7. `npm install`
69
+
70
+ **Exit**: Empty project builds with `npm run build`
71
+
72
+ ---
73
+
74
+ ### Phase 1 — Imports & JSX
75
+
76
+ **Entry**: Source files copied to `src/`
77
+
78
+ **Actions** (bulk sed — see `references/codemod-commands.md`):
79
+ 1. Preact → React: `from "preact/hooks"` → `from "react"`, etc.
80
+ 2. `ComponentChildren` → `ReactNode`
81
+ 3. `class=` → `className=` in JSX
82
+ 4. SVG attrs: `stroke-width` → `strokeWidth`, `fill-rule` → `fillRule`, etc.
83
+ 5. HTML attrs: `for=` → `htmlFor=`, `fetchpriority` → `fetchPriority`
84
+ 6. Remove `/** @jsxRuntime automatic */` pragma comments
85
+
86
+ **Verification**: `grep -r 'from "preact' src/ | wc -l` → 0
87
+
88
+ **Exit**: Zero preact imports, zero `class=` in JSX
89
+
90
+ See: `references/imports/README.md`, `references/jsx-migration.md`
91
+
92
+ ---
93
+
94
+ ### Phase 2 — Signals & State
95
+
96
+ **Entry**: Phase 1 complete
97
+
98
+ **Actions**:
99
+ 1. Bulk: `from "@preact/signals"` → `from "@decocms/start/sdk/signal"` (module-level signals)
100
+ 2. Manual: `useSignal(val)` → `useState(val)` (component hooks)
101
+ 3. Manual: `useComputed(() => expr)` → `useMemo(() => expr, [deps])` (component hooks)
102
+ 4. For global reactive state: use `signal()` from `@decocms/start/sdk/signal` + `useStore()` from `@tanstack/react-store`
103
+
104
+ **Verification**: `grep -r '@preact/signals' src/ | wc -l` → 0
105
+
106
+ **Exit**: Zero @preact/signals imports
107
+
108
+ See: `references/signals/README.md`, `references/react-signals-state.md`
109
+
110
+ ---
111
+
112
+ ### Phase 3 — Deco Framework
113
+
114
+ **Entry**: Phase 2 complete
115
+
116
+ **Actions** (mostly bulk sed):
117
+ 1. Remove `$fresh/runtime.ts` imports (`asset()` → identity, `IS_BROWSER` → `typeof window !== "undefined"`)
118
+ 2. `from "deco-sites/SITENAME/"` → `from "~/"`
119
+ 3. `from "$store/"` → `from "~/"`
120
+ 4. `from "site/"` → `from "~/"`
121
+ 5. `SectionProps` → inline type
122
+ 6. `useScript` → `from "@decocms/start/sdk/useScript"`
123
+ 7. `clx` → `from "@decocms/start/sdk/clx"`
124
+
125
+ **Verification**: `grep -rE 'from "(@deco/deco|\$fresh|deco-sites/)' src/ | wc -l` → 0
126
+
127
+ **Exit**: Zero @deco/deco, $fresh, deco-sites/ imports
128
+
129
+ See: `references/deco-framework/README.md`
130
+
131
+ ---
132
+
133
+ ### Phase 4 — Commerce & Types
134
+
135
+ **Entry**: Phase 3 complete
136
+
137
+ **Actions**:
138
+ 1. `from "apps/commerce/types.ts"` → `from "@decocms/apps/commerce/types"`
139
+ 2. `from "apps/admin/widgets.ts"` → `from "~/types/widgets"` (create local file)
140
+ 3. `from "apps/website/components/Image.tsx"` → `from "~/components/ui/Image"` (create local)
141
+ 4. SDK utilities: `~/sdk/useOffer` → `@decocms/apps/commerce/sdk/useOffer`, etc.
142
+
143
+ **Verification**: `grep -r 'from "apps/' src/ | wc -l` → 0
144
+
145
+ **Exit**: Zero `apps/` imports
146
+
147
+ See: `references/commerce/README.md`, `references/vtex-commerce.md`
148
+
149
+ ---
150
+
151
+ ### Phase 5 — Platform Hooks
152
+
153
+ **Entry**: Phase 4 complete
154
+
155
+ **Actions** (manual implementation):
156
+ 1. Create `src/hooks/useCart.ts` — module-level singleton + listener pattern
157
+ 2. Create `src/hooks/useUser.ts`, `src/hooks/useWishlist.ts` (stubs or real)
158
+ 3. Wire VTEX API calls via `@decocms/apps` invoke functions
159
+
160
+ **Exit**: Cart add/remove works, no `apps/{platform}/hooks` imports
161
+
162
+ See: `references/platform-hooks/README.md`
163
+
164
+ ---
165
+
166
+ ### Phase 6 — Islands Elimination
167
+
168
+ **Entry**: Phase 5 complete
169
+
170
+ **Actions**:
171
+ 1. Audit `src/islands/` — categorize each file:
172
+ - **Wrapper**: just re-exports from `components/` → delete, repoint imports
173
+ - **Standalone**: has real logic → move to `src/components/`
174
+ 2. Update all imports pointing to `islands/` to point to `components/`
175
+ 3. Delete `src/islands/` directory
176
+
177
+ **Verification**: `ls src/islands/ 2>/dev/null` → directory not found
178
+
179
+ **Exit**: No islands/ directory
180
+
181
+ See: `references/islands.md`
182
+
183
+ ---
184
+
185
+ ### Phase 7 — Section Registry
186
+
187
+ **Entry**: Phase 6 complete
188
+
189
+ **Actions** (critical — build `src/setup.ts`):
190
+ 1. Register all sections via `registerSections()` with dynamic imports
191
+ 2. Register critical sections (Header, Footer) via `registerSectionsSync()` + `setResolvedComponent()`
192
+ 3. Register section loaders via `registerSectionLoaders()`
193
+ 4. Register layout sections via `registerLayoutSections()`
194
+ 5. Register commerce loaders via `registerCommerceLoaders()` with SWR caching
195
+ 6. Wire `onBeforeResolve()` → `initVtexFromBlocks()` for VTEX config
196
+ 7. Configure `setAsyncRenderingConfig()` with `alwaysEager` for critical sections
197
+ 8. Configure admin: `setMetaData()`, `setRenderShell()`, `setInvokeLoaders()`
198
+
199
+ **Template**: `templates/setup-ts.md`
200
+
201
+ **Exit**: `setup.ts` compiles, all sections registered
202
+
203
+ See: `references/async-rendering.md` (Part 2: Site Implementation)
204
+
205
+ ---
206
+
207
+ ### Phase 8 — Routes & CMS
208
+
209
+ **Entry**: Phase 7 complete
210
+
211
+ **Actions**:
212
+ 1. Create `src/router.tsx` with scroll restoration
213
+ 2. Create `src/routes/__root.tsx` with QueryClient, LiveControls, NavigationProgress, analytics
214
+ 3. Create `src/routes/index.tsx` using `cmsHomeRouteConfig()`
215
+ 4. Create `src/routes/$.tsx` using `cmsRouteConfig()`
216
+
217
+ **Templates**: `templates/root-route.md`, `templates/router.md`
218
+
219
+ **Exit**: Routes compile, CMS pages resolve
220
+
221
+ See: `references/navigation.md`
222
+
223
+ ---
224
+
225
+ ### Phase 9 — Worker Entry
226
+
227
+ **Entry**: Phase 8 complete
228
+
229
+ **Actions**:
230
+ 1. Create `src/server.ts` — **CRITICAL: `import "./setup"` MUST be the first line**
231
+ 2. Create `src/worker-entry.ts` — same: `import "./setup"` first
232
+ 3. Wire admin handlers (handleMeta, handleDecofileRead, handleRender)
233
+ 4. Wire VTEX proxy if needed
234
+
235
+ **Template**: `templates/worker-entry.md`
236
+
237
+ **CRITICAL**: Without `import "./setup"` as the first import, server functions in Vite split modules will have empty state. This causes 404 on client-side navigation.
238
+
239
+ **Exit**: `npm run dev` serves pages, admin endpoints work
240
+
241
+ See: `references/worker-cloudflare.md`
242
+
243
+ ---
244
+
245
+ ### Phase 10 — Matchers
246
+
247
+ **Entry**: Phase 9 complete
248
+
249
+ **Actions**:
250
+ 1. Audit existing matchers (check `src/matchers/`, `src/sdk/matcher*`)
251
+ 2. Migrate MatchContext → MatcherContext (different shape)
252
+ 3. Register matchers in `setup.ts` via `registerMatcher()`
253
+ 4. Wire CF geo cookie injection if using location matchers
254
+
255
+ **Exit**: All matchers registered, flags/variants work
256
+
257
+ See: `references/matchers.md`
258
+
259
+ ---
260
+
261
+ ### Phase 11 — Async Rendering
262
+
263
+ **Entry**: Phase 10 complete (site builds and serves pages)
264
+
265
+ **Actions**:
266
+ 1. Identify lazy sections from CMS Lazy wrappers
267
+ 2. Add `export function LoadingFallback()` to lazy sections
268
+ 3. Configure `registerCacheableSections()` for SWR on heavy sections
269
+ 4. Test deferred section loading on scroll
270
+
271
+ **Exit**: Above-the-fold renders instantly, below-fold loads on scroll
272
+
273
+ See: `references/async-rendering.md`
274
+
275
+ ---
276
+
277
+ ### Phase 12 — Search
278
+
279
+ **Entry**: Phase 11 complete
280
+
281
+ **Actions**:
282
+ 1. Wire search route with `loaderDeps` for URL params (`q`, `sort`, `page`, filters)
283
+ 2. Configure VTEX Intelligent Search loader
284
+ 3. Wire SearchBar autocomplete via server function
285
+ 4. Test filter toggling, pagination, sort
286
+
287
+ **Exit**: Search page works end-to-end
288
+
289
+ See: `references/search.md`
290
+
291
+ ---
292
+
293
+ ## Post-Migration
294
+
295
+ | Problem | Reference |
296
+ |---------|-----------|
297
+ | Hydration mismatches, flash-of-white, CLS | `references/hydration-fixes.md` |
298
+ | Runtime bugs, nested sections, VTEX resilience | `references/storefront-patterns.md` |
299
+ | CSS / Tailwind / DaisyUI issues | `references/css-styling.md` |
300
+ | Admin / CMS integration issues | `references/admin-cms.md` |
301
+ | React hooks patterns | `references/react-hooks-patterns.md` |
302
+ | All indexed gotchas | `references/gotchas.md` |
303
+
304
+ ## Key Principles
305
+
306
+ 1. **No compat layer anywhere** -- not in `@decocms/start`, not in `@decocms/apps`, not in the site repo
307
+ 2. **Replace, don't wrap** -- change the import to the real thing, don't create a pass-through
308
+ 3. **Types from the library, UI from the site** -- `Product` type comes from `@decocms/apps/commerce/types`, but the `<Image>` component is site-local
309
+ 4. **One Vite alias maximum** -- `"~"` -> `"src/"` is the only acceptable alias
310
+ 5. **`tsconfig.json` mirrors `vite.config.ts`** -- only `"~/*": ["./src/*"]` in paths
311
+ 6. **Signals don't auto-subscribe in React** -- reading `signal.value` in render creates NO subscription; use `useStore(signal.store)` from `@tanstack/react-store`
312
+ 7. **Commerce loaders need request context** -- `resolve.ts` must pass URL/path to PLP/PDP loaders
313
+ 8. **`wrangler.jsonc` main must be a custom worker-entry** -- TanStack Start ignores `export default` in `server.ts`
314
+ 9. **Copy components faithfully, never rewrite** -- `cp` the original, then only change mechanical things (class→className, imports). NEVER regenerate or "improve" — AI-rewritten components are the #1 source of visual regressions
315
+ 10. **Tailwind v4 logical property hazard** -- mixed `px-*` + `pl-*/pr-*` on the same element breaks the cascade
316
+ 11. **oklch CSS variables need triplets, not hex** -- `oklch(var(--x))` must store variables as oklch triplets
317
+ 12. **Verify ALL imports resolve at runtime, not just build** -- Vite tree-shakes dead imports, so `npm run build` passes even with missing modules
318
+ 13. **`import "./setup"` first** — in both `server.ts` and `worker-entry.ts`
319
+ 14. **globalThis for split modules** — Vite server function split modules need `globalThis.__deco` to share state
320
+
321
+ ## Worker Entry Architecture
322
+
323
+ Admin routes MUST be handled in `createDecoWorkerEntry` (the outermost wrapper), NOT inside TanStack's `createServerEntry`. Vite strips custom logic from `createServerEntry` in production.
324
+
325
+ ```
326
+ Request
327
+ └─> createDecoWorkerEntry(serverEntry, { admin: { ... } })
328
+ ├─> tryAdminRoute() ← FIRST: /live/_meta, /.decofile, /live/previews/*
329
+ ├─> cache purge check ← __deco_purge_cache
330
+ ├─> static asset bypass ← /assets/*, favicon, sprites
331
+ ├─> Cloudflare cache (caches.open)
332
+ └─> serverEntry.fetch() ← TanStack Start handles everything else
333
+ ```
334
+
335
+ Key rules:
336
+ - `./setup` MUST be imported first
337
+ - Admin handlers passed as options, NOT imported inside `createDecoWorkerEntry`
338
+ - `/live/` and `/.decofile` are in `DEFAULT_BYPASS_PATHS` -- never cached
339
+
340
+ ## Conductor / AI Bulk Migration Workflow
341
+
342
+ For sites with 100+ sections:
343
+
344
+ 1. **Scaffold + Copy** (human): scaffold project, `cp -r src/`, set up config files
345
+ 2. **Mechanical Rewrites** (AI/conductor): bulk import rewrites, JSX attr rewrites, type rewrites, signal-to-state — see `references/codemod-commands.md`
346
+ 3. **Verify** (human + AI): `npx tsc --noEmit`, `npm run build`, `npm run dev` + browser test, visual comparison
347
+ 4. **Fix Runtime Issues** (human-guided): gotchas and architectural differences
348
+
349
+ **Key Insight**: The approach that worked (836 errors → 0 across 213 files) treated every file as: copy the original, apply mechanical changes only. Never "rewrite in React".
350
+
351
+ ## Reference Index
352
+
353
+ | Topic | Path |
354
+ |-------|------|
355
+ | Preact → React imports | `references/imports/` |
356
+ | Signals → TanStack Store | `references/signals/` |
357
+ | Deco framework elimination | `references/deco-framework/` |
358
+ | Commerce & widget types | `references/commerce/` |
359
+ | Platform hooks (VTEX) | `references/platform-hooks/` |
360
+ | Vite configuration | `references/vite-config/` |
361
+ | Automation commands | `references/codemod-commands.md` |
362
+ | Islands elimination | `references/islands.md` |
363
+ | Navigation & routing | `references/navigation.md` |
364
+ | Search implementation | `references/search.md` |
365
+ | Matchers (architecture + migration) | `references/matchers.md` |
366
+ | Async rendering (architecture + site guide) | `references/async-rendering.md` |
367
+ | Hydration fixes | `references/hydration-fixes.md` |
368
+ | Runtime storefront patterns | `references/storefront-patterns.md` |
369
+ | Admin / CMS integration | `references/admin-cms.md` |
370
+ | Gotchas index | `references/gotchas.md` |
371
+ | React hooks patterns | `references/react-hooks-patterns.md` |
372
+ | React signals & state | `references/react-signals-state.md` |
373
+ | JSX migration differences | `references/jsx-migration.md` |
374
+ | VTEX commerce gotchas | `references/vtex-commerce.md` |
375
+ | Worker / Cloudflare / build | `references/worker-cloudflare.md` |
376
+ | CSS / Tailwind / DaisyUI | `references/css-styling.md` |
377
+ | setup.ts template | `templates/setup-ts.md` |
378
+ | vite.config.ts template | `templates/vite-config.md` |
379
+ | worker-entry template | `templates/worker-entry.md` |
380
+ | __root.tsx template | `templates/root-route.md` |
381
+ | router.tsx template | `templates/router.md` |
382
+ | package.json template | `templates/package-json.md` |
@@ -0,0 +1,154 @@
1
+ # Admin / CMS Integration Gotchas
2
+
3
+ > loadDeferredSection, schema refs, section-type props, device context, hydration.
4
+
5
+
6
+ ## 16. Admin Route Cache Bypass
7
+
8
+ `/live/` and `/.decofile` are in `DEFAULT_BYPASS_PATHS`. Admin routes are intercepted before caching.
9
+
10
+
11
+ ## 18. Loader References in JSON Schema
12
+
13
+ `Resolvable` definition with `additionalProperties: true` needed for props that accept loader refs.
14
+
15
+ ---
16
+
17
+
18
+ ## 23. Custom useId with Math.random() Causes Hydration Mismatch
19
+
20
+ 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.
21
+
22
+ **Fix**: Replace with React's native `useId`:
23
+
24
+ ```typescript
25
+ import { useId as useReactId } from "react";
26
+ export const useId = useReactId;
27
+ ```
28
+
29
+
30
+ ## 29. Device Context Must Be Server-Driven, Not Hardcoded
31
+
32
+ **Severity**: HIGH — breaks entire page layout (mobile vs desktop)
33
+
34
+ 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.
35
+
36
+ **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`.
37
+
38
+ **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.
39
+
40
+ **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>`:
41
+
42
+ ```typescript
43
+ // In routes/index.tsx or routes/$.tsx
44
+ const MOBILE_RE = /mobile|android|iphone|ipad|ipod|webos|blackberry|opera mini|iemobile/i;
45
+
46
+ const loadPage = createServerFn({ method: "GET" }).handler(async () => {
47
+ const ua = getRequestHeader("user-agent") ?? "";
48
+ const matcherCtx = { userAgent: ua, url: getRequestUrl().toString(), path: "/", cookies: getCookies() };
49
+ const page = await resolveDecoPage("/", matcherCtx);
50
+ return { page, isMobile: MOBILE_RE.test(ua) };
51
+ });
52
+
53
+ function HomePage() {
54
+ const { page, isMobile } = Route.useLoaderData();
55
+ return (
56
+ <Device.Provider value={{ isMobile }}>
57
+ <DecoPageRenderer sections={page.resolvedSections} />
58
+ </Device.Provider>
59
+ );
60
+ }
61
+ ```
62
+
63
+ Remove the hardcoded `<Device.Provider value={{ isMobile: true }}>` from `__root.tsx`.
64
+
65
+ **Key constraint**: Do NOT put `createServerFn` in `__root.tsx` — TanStack Start's server function splitter cannot handle it there.
66
+
67
+ ---
68
+
69
+ ## Admin Preview HTML Shell
70
+
71
+ The preview at `/live/previews/*` renders sections into an HTML shell. This shell MUST match the production `<html>` attributes for CSS frameworks to work:
72
+
73
+ ```typescript
74
+ // In setup.ts
75
+ setRenderShell({
76
+ css: appCss, // Vite ?url import of app.css
77
+ fonts: ["https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap"],
78
+ theme: "light", // -> <html data-theme="light"> (required for DaisyUI v4)
79
+ bodyClass: "bg-base-100 text-base-content",
80
+ lang: "pt-BR",
81
+ });
82
+ ```
83
+
84
+ Without `data-theme="light"`, DaisyUI v4 theme variables (`--color-primary`, etc.) won't activate in the preview iframe, causing color mismatches vs production.
85
+
86
+ ## Client-Safe vs Server-Only Imports
87
+
88
+ `@decocms/start` has two admin entry points:
89
+ - **`@decocms/start/admin`** -- server-only handlers (handleMeta, handleRender, etc.) -- these may transitively import `node:async_hooks`
90
+ - **`@decocms/start/admin/setup`** (re-exported from `@decocms/start/admin`) -- client-safe setup functions (setMetaData, setInvokeLoaders, setRenderShell) -- NO node: imports
91
+
92
+ The site's `setup.ts` can safely import from `@decocms/start/admin` because it only uses the setup functions. But the barrel export must be structured so Vite tree-shaking doesn't pull server modules into client bundles.
93
+
94
+ ## Admin Self-Hosting Architecture
95
+
96
+ When a site is self-hosted (deployed to its own Cloudflare Worker), the admin communicates with the storefront via the `productionUrl`:
97
+
98
+ ```
99
+ admin.deco.cx
100
+ └─> createContentSiteSDK (when env.platform === "content" OR devContentUrl is set)
101
+ ├─> fetch(productionUrl + "/live/_meta") ← schema + manifest
102
+ ├─> fetch(productionUrl + "/.decofile") ← content blocks
103
+ └─> iframe src = productionUrl + "/live/previews/*" ← section preview
104
+ ```
105
+
106
+ ### Content URL Resolution Priority
107
+
108
+ 1. `devContentUrl` URL param → saved to `localStorage[deco::devContentUrl::${site}]` → used by Content SDK
109
+ 2. `devContentUrl` from localStorage → used by Content SDK
110
+ 3. `site.metadata.selfHosting.productionUrl` (Supabase) → used by Content SDK
111
+ 4. `https://${site}.deco.site` → fallback
112
+
113
+ ### Environment Platform Gate
114
+
115
+ The admin only uses `createContentSiteSDK` when:
116
+ - `devContentUrl` is set (localStorage or URL param), OR
117
+ - The current environment has `platform: "content"`
118
+
119
+ Setting `productionUrl` in Supabase alone is NOT sufficient. The environment must be "content" platform.
120
+
121
+ For local dev, use the URL param shortcut:
122
+ ```
123
+ https://admin.deco.cx/sites/YOUR_SITE/spaces/...?devContentUrl=http://localhost:5181
124
+ ```
125
+
126
+ ## Admin / CMS Schema Architecture
127
+
128
+ The deco admin communicates with the storefront via:
129
+ - `GET /live/_meta` -- returns full JSON Schema + manifest of block types
130
+ - `GET /.decofile` -- returns the site's content blocks
131
+ - `POST /deco/render` -- renders a section/page with given props in an iframe
132
+ - `POST /deco/invoke` -- calls a loader/action and returns JSON
133
+
134
+ ### Schema Composition (`composeMeta`)
135
+
136
+ ```
137
+ [generate-schema.ts] --> meta.gen.json (sections only, pages: empty)
138
+ [setup.ts] --> imports meta.gen.json --> calls setMetaData(metaData)
139
+ [setMetaData] --> calls composeMeta() --> injects page schema + merges definitions
140
+ [/live/_meta] --> returns composed schema with content-hash ETag
141
+ ```
142
+
143
+ Key rules:
144
+ - `toBase64()` MUST produce padded Base64 (matching `btoa()`) -- admin uses `btoa()` to construct definition refs
145
+ - Page schema uses flat properties (no allOf + @Props indirection) to minimize RJSF resolution steps
146
+ - ETag is a content-based DJB2 hash, not string length, for reliable cache invalidation
147
+
148
+ ### Admin Local Development
149
+
150
+ 1. Start admin: `cd admin && deno task play` (port 4200)
151
+ 2. Start storefront: `bun run dev` (port 5181)
152
+ 3. Set devContentUrl: `localStorage.setItem('deco::devContentUrl::YOUR_SITE_NAME', 'http://localhost:PORT')`
153
+ 4. Navigate to `http://localhost:4200/sites/YOUR_SITE_NAME/spaces/pages`
154
+ 5. After schema changes: clear admin cache and hard-refresh