@decocms/start 2.12.0 → 2.14.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 (27) hide show
  1. package/.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +32 -6
  2. package/CLAUDE.md +1 -1
  3. package/package.json +1 -1
  4. package/scripts/migrate/phase-cleanup-audit.test.ts +137 -0
  5. package/scripts/migrate/phase-cleanup-audit.ts +105 -0
  6. package/scripts/migrate/post-cleanup/rules.ts +77 -6
  7. package/scripts/migrate/post-cleanup/runner.test.ts +123 -2
  8. package/scripts/migrate/post-cleanup/shim-classify.test.ts +352 -0
  9. package/scripts/migrate/post-cleanup/shim-classify.ts +246 -0
  10. package/scripts/migrate.ts +36 -8
  11. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +0 -655
  12. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +0 -174
  13. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +0 -78
  14. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +0 -174
  15. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +0 -834
  16. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +0 -70
  17. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +0 -121
  18. package/.cursor/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +0 -231
  19. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +0 -220
  20. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +0 -103
  21. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +0 -75
  22. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +0 -127
  23. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +0 -96
  24. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +0 -148
  25. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +0 -197
  26. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +0 -67
  27. /package/{.cursor → .agents}/skills/deco-to-tanstack-migration/references/server-functions/README.md +0 -0
@@ -1,655 +0,0 @@
1
- ---
2
- name: deco-to-tanstack-migration
3
- description: Migrate Deco.cx storefronts from Fresh/Preact to TanStack Start/React on Cloudflare Workers. Phase-based playbook with automation scripts, battle-tested templates, and cross-references to specialized skills. Use when migrating a deco-site, porting Preact components to React, or setting up TanStack Start for a Deco storefront.
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 (VTEX, 100+ sections) and storefront-tanstack (Shopify).
9
-
10
- **Framework minimum**: `@decocms/start >= 1.6.2` (deferred sections with `:slug` route params require the `routeParams` propagation fix — see gotcha #47).
11
-
12
- ## One-Prompt Migration (copy/paste to an AI agent)
13
-
14
- Paste the prompt below verbatim into a code-writing AI (Claude Code, Cursor, etc.) pointed at a fresh TanStack Start scaffold that already has the source `deco-sites/*` project copied to `.context/` for reference. The agent will execute phases 0–10 sequentially, gating on exit criteria, and stop to confirm when a phase needs a human judgment call.
15
-
16
- ```
17
- You are migrating a Deco/Fresh/Preact storefront to TanStack Start/React/Cloudflare Workers.
18
-
19
- Source site reference: .context/<SITE_NAME>/ (read-only)
20
- Target repo: this working directory
21
- Framework docs: .cursor/skills/deco-to-tanstack-migration/SKILL.md + references/ + templates/
22
-
23
- Execute the migration in phase order. For each phase:
24
- 1. Read the phase section in SKILL.md and the linked references/templates
25
- 2. Run the automation commands from references/codemod-commands.md where the phase is bulk-safe
26
- 3. Verify the phase's exit criteria before moving on
27
- 4. If a verification fails, fix root causes — do not skip
28
-
29
- Critical invariants (violate none of these):
30
- - Never add compat/ layers or Vite aliases beyond `~` → `src/`
31
- - Copy components faithfully from source — do NOT rewrite JSX, class names, or grid/flex structure. Only apply mechanical migrations (class→className, for→htmlFor, preact→react imports). AI-rewritten components are the #1 regression source.
32
- - `@decocms/start >= 1.6.2` in package.json (deferred-section routeParams fix)
33
- - `import "./setup"` is the FIRST line in server.ts and worker-entry.ts
34
- - Use `createSiteSetup()` + `applySectionConventions()` + `autoconfigApps(blocks, APP_REGISTRY)` — do NOT hand-wire section loaders, alwaysEager lists, or commerce loaders for apps the registry already covers (Phase 7 reference has the shape)
35
- - Section metadata is declared in the section file (`export const eager = true`, `export const cache = "listing"`, etc.) — `generate-sections.ts` extracts it. Do not maintain duplicate arrays in setup.ts.
36
- - After every phase, run: `npm run build && npm run typecheck`. Both must be green before proceeding.
37
-
38
- Stop and ask the human when:
39
- - A component's layout output differs visibly from the source reference
40
- - A gotcha listed in references/gotchas.md applies but the fix is non-mechanical
41
- - Phase 5 (commerce/platform hooks): cart/user/wishlist flows are platform-specific and require real credentials — confirm the hook implementation before writing
42
- - Phase 8 (routes): SEO sections, SSR/SPA decisions, and device detection need human confirmation
43
-
44
- Start at Phase 0. Report phase completion with a one-line summary + the verification command output.
45
- ```
46
-
47
- The prompt assumes:
48
- - The target repo has TanStack Start scaffolded (`npm create @tanstack/app@latest -- --template cloudflare-workers`)
49
- - `@decocms/start` and `@decocms/apps` are published and installable (or locally linked)
50
- - The source `src/` has been copied and `.deco/blocks/` is present
51
-
52
- If either is missing, the agent will stop at Phase 0 and ask.
53
-
54
- ## Architecture Boundaries
55
-
56
- | Layer | npm Package | Purpose | Must NOT Contain |
57
- |-------|-------------|---------|-----------------|
58
- | **@decocms/start** | `@decocms/start` | CMS resolution, DecoPageRenderer, worker entry, sdk (useScript, signal, clx) | Preact shims, widget types, site-specific maps |
59
- | **@decocms/apps** | `@decocms/apps` | VTEX/Shopify loaders, commerce types, commerce sdk (useOffer, formatPrice, analytics) | Passthrough HTML components, Preact/Fresh refs |
60
- | **Site repo** | (not published) | All UI: components, hooks, types, routes, styles | No compat/ layer, no aliases beyond `~` |
61
-
62
- ### What Belongs Where
63
-
64
- ```
65
- @decocms/start (framework)
66
- ├── src/cms/ # Block loading, page resolution, section registry
67
- │ └── loader.ts # loadBlocks, setBlocks, AsyncLocalStorage for per-request overrides
68
- ├── src/admin/ # Admin protocol: meta, decofile, invoke, render, schema composition
69
- │ ├── meta.ts # setMetaData() calls composeMeta() at startup; /deco/meta handler
70
- │ ├── schema.ts # MetaResponse type, composeMeta(), framework block schemas (pages)
71
- │ ├── render.ts # /live/previews/* for section + page preview (HTML shell)
72
- │ ├── setup.ts # Client-safe setup (setMetaData, setInvokeLoaders, setRenderShell)
73
- │ ├── decofile.ts # /.decofile read/reload
74
- │ ├── invoke.ts # /deco/invoke for loader/action calls
75
- │ ├── cors.ts # CORS + admin origin validation
76
- │ └── liveControls.ts # Admin iframe bridge postMessage script
77
- ├── src/sdk/
78
- │ ├── workerEntry.ts # createDecoWorkerEntry: outermost Cloudflare Worker wrapper
79
- │ ├── useScript.ts, signal.ts, clx.ts, cachedLoader.ts, instrumentedFetch.ts
80
- │ └── ...
81
- ├── src/hooks/ # DecoPageRenderer (uses registry, NOT hardcoded map), LiveControls
82
- ├── src/types/ # FnContext, App, AppContext, Section, SectionProps, Resolved
83
- └── scripts/ # generate-blocks.ts, generate-schema.ts
84
-
85
- @decocms/apps (commerce)
86
- ├── commerce/types/ # Product, AnalyticsItem, BreadcrumbList, Filter, etc.
87
- ├── commerce/utils/ # mapProductToAnalyticsItem, parseRange, formatRange
88
- ├── commerce/sdk/ # useOffer, useVariantPossibilities, formatPrice, relative, analytics
89
- ├── vtex/ # Client, loaders (actual VTEX API calls)
90
- └── shopify/ # Client, loaders (actual Shopify API calls)
91
-
92
- site repo (UI + business logic)
93
- ├── src/components/ # All UI components (Image, Picture, Seo, Theme, etc.)
94
- ├── src/hooks/ # useCart (real VTEX implementation), useUser, useWishlist
95
- ├── src/types/ # widgets.ts (string aliases), vtex.ts (OrderFormItem, etc.)
96
- ├── src/sdk/ # Site-specific contexts, usePlatform, useUI, useSuggestions
97
- ├── src/sections/ # All CMS-renderable sections
98
- ├── src/routes/ # TanStack Router routes
99
- └── src/server/ # Server functions (invoke.ts — createServerFn wrappers for @decocms/apps)
100
- ```
101
-
102
- ### Architecture Map
103
-
104
- | Old Stack | New Stack |
105
- |-----------|-----------|
106
- | Deno + Fresh | Node + TanStack Start |
107
- | Preact + Islands | React 19 + React Compiler |
108
- | @preact/signals | @tanstack/store + @tanstack/react-store |
109
- | Deco CMS runtime | Static JSON blocks via @decocms/start |
110
- | $fresh/runtime.ts | Inlined (asset() removed, IS_BROWSER inlined) |
111
- | @deco/deco/* | @decocms/start/sdk/* or inline stubs |
112
- | apps/commerce/types | @decocms/apps/commerce/types |
113
- | apps/website/components/* | ~/components/ui/* (local React) |
114
- | apps/{platform}/hooks/* | ~/hooks/useCart (real implementation) |
115
- | ~/sdk/useOffer | @decocms/apps/commerce/sdk/useOffer |
116
- | ~/sdk/useScript | @decocms/start/sdk/useScript |
117
- | ~/sdk/signal | @decocms/start/sdk/signal |
118
-
119
- ## Migration Phases
120
-
121
- Each phase has entry/exit criteria. Follow in order. Automation % indicates how much can be done with bulk sed/grep.
122
-
123
- | Phase | Name | Automation | Related Skill |
124
- |-------|------|-----------|---------------|
125
- | [0](#phase-0--scaffold) | Scaffold & Copy | 100% | — |
126
- | [1](#phase-1--imports--jsx) | Import Rewrites | ~90% | — |
127
- | [2](#phase-2--signals--state) | Signals & State | ~50% | — |
128
- | [3](#phase-3--deco-framework) | Deco Framework Elimination | ~80% | — |
129
- | [4](#phase-4--commerce--types) | Commerce Types & UI | ~70% | deco-apps-vtex-porting |
130
- | [5](#phase-5--platform-hooks) | Platform Hooks | 0% | deco-apps-vtex-porting |
131
- | [6](#phase-6--islands-elimination) | Islands Elimination | ~60% | deco-islands-migration |
132
- | [7](#phase-7--section-registry) | Section Registry & Setup | 0% | deco-async-rendering-site-guide |
133
- | [8](#phase-8--routes--cms) | Routes & CMS | template | deco-tanstack-navigation |
134
- | [9](#phase-9--worker-entry) | Worker Entry & Server | template | deco-edge-caching |
135
- | [10](#phase-10--async-rendering) | Async Rendering & Polish | 0% | deco-async-rendering-site-guide |
136
-
137
- ---
138
-
139
- ### Phase 0 — Scaffold
140
-
141
- **Entry**: Source site accessible, @decocms/start + @decocms/apps published
142
-
143
- **Actions**:
144
- 1. Create TanStack Start project
145
- 2. Copy `src/components/`, `src/sections/`, `src/islands/`, `src/hooks/`, `src/sdk/`, `src/loaders/` from source
146
- 3. Copy `.deco/blocks/` (CMS content)
147
- 4. Copy `static/` assets
148
- 5. Create `package.json` — see `templates/package-json.md`
149
- 6. Create `vite.config.ts` — see `templates/vite-config.md`
150
- 7. `npm install`
151
-
152
- **Exit**: Empty project builds with `npm run build`
153
-
154
- ---
155
-
156
- ### Phase 1 — Imports & JSX
157
-
158
- **Entry**: Source files copied to `src/`
159
-
160
- **Actions** (bulk sed — see `references/codemod-commands.md`):
161
- 1. Preact → React: `from "preact/hooks"` → `from "react"`, etc.
162
- 2. `ComponentChildren` → `ReactNode`
163
- 3. `class=` → `className=` in JSX
164
- 4. SVG attrs: `stroke-width` → `strokeWidth`, `fill-rule` → `fillRule`, etc.
165
- 5. HTML attrs: `for=` → `htmlFor=`, `fetchpriority` → `fetchPriority`, `autocomplete` → `autoComplete`
166
- 6. Remove `/** @jsxRuntime automatic */` pragma comments
167
-
168
- **Verification**: `grep -r 'from "preact' src/ | wc -l` → 0
169
-
170
- **Exit**: Zero preact imports, zero `class=` in JSX
171
-
172
- See: `references/imports/README.md`
173
-
174
- ---
175
-
176
- ### Phase 2 — Signals & State
177
-
178
- **Entry**: Phase 1 complete
179
-
180
- **Actions**:
181
- 1. Bulk: `from "@preact/signals"` → `from "@decocms/start/sdk/signal"` (module-level signals)
182
- 2. Manual: `useSignal(val)` → `useState(val)` (component hooks)
183
- 3. Manual: `useComputed(() => expr)` → `useMemo(() => expr, [deps])` (component hooks)
184
- 4. For global reactive state: use `signal()` from `@decocms/start/sdk/signal` + `useStore()` from `@tanstack/react-store`
185
-
186
- **Verification**: `grep -r '@preact/signals' src/ | wc -l` → 0
187
-
188
- **Exit**: Zero @preact/signals imports
189
-
190
- See: `references/signals/README.md`
191
-
192
- ---
193
-
194
- ### Phase 3 — Deco Framework
195
-
196
- **Entry**: Phase 2 complete
197
-
198
- **Actions** (mostly bulk sed):
199
- 1. Remove `$fresh/runtime.ts` imports (`asset()` → identity, `IS_BROWSER` → `typeof window !== "undefined"`)
200
- 2. `from "deco-sites/SITENAME/"` → `from "~/"`
201
- 3. `from "$store/"` → `from "~/"`
202
- 4. `from "site/"` → `from "~/"`
203
- 5. `SectionProps` → inline type or `import { SectionProps } from "~/types/section"`
204
- 6. `useScript` → `from "@decocms/start/sdk/useScript"`
205
- 7. `clx` → `from "@decocms/start/sdk/clx"`
206
-
207
- **Verification**: `grep -rE 'from "(@deco/deco|\$fresh|deco-sites/)' src/ | wc -l` → 0
208
-
209
- **Exit**: Zero @deco/deco, $fresh, deco-sites/ imports
210
-
211
- See: `references/deco-framework/README.md`
212
-
213
- ---
214
-
215
- ### Phase 4 — Commerce & Types
216
-
217
- **Entry**: Phase 3 complete
218
-
219
- **Actions**:
220
- 1. `from "apps/commerce/types.ts"` → `from "@decocms/apps/commerce/types"`
221
- 2. `from "apps/admin/widgets.ts"` → `from "@decocms/start/types/widgets"` (framework owns the string aliases — `ImageWidget`, `HTMLWidget`, etc.; do **not** create a local `src/types/widgets.ts`)
222
- 3. `from "apps/website/components/Image.tsx"` → `from "~/components/ui/Image"` (create local components)
223
- 4. SDK utilities: `~/sdk/useOffer` → `@decocms/apps/commerce/sdk/useOffer`, `~/sdk/format` → `@decocms/apps/commerce/sdk/formatPrice`, etc.
224
-
225
- **Verification**: `grep -r 'from "apps/' src/ | wc -l` → 0
226
-
227
- **Exit**: Zero `apps/` imports
228
-
229
- See: `references/commerce/README.md`
230
-
231
- ---
232
-
233
- ### Phase 5 — Platform Hooks
234
-
235
- **Entry**: Phase 4 complete
236
-
237
- **Actions** (manual implementation):
238
- 1. Create `src/hooks/useCart.ts` — module-level singleton + listener pattern
239
- 2. Create `src/hooks/useUser.ts`, `src/hooks/useWishlist.ts` (stubs or real)
240
- 3. Wire VTEX API calls via `@decocms/apps` invoke functions
241
-
242
- **Pattern**: Closure state + `_listeners` Set + `useState` for re-renders. See espacosmart's useCart.ts as template.
243
-
244
- **Exit**: Cart add/remove works, no `apps/{platform}/hooks` imports
245
-
246
- See: `references/platform-hooks/README.md`, skill `deco-apps-vtex-porting`
247
-
248
- ---
249
-
250
- ### Phase 6 — Islands Elimination
251
-
252
- **Entry**: Phase 5 complete
253
-
254
- **Actions**:
255
- 1. Audit `src/islands/` — categorize each file:
256
- - **Wrapper**: just re-exports from `components/` → delete, repoint imports
257
- - **Standalone**: has real logic → move to `src/components/`
258
- 2. Update all imports pointing to `islands/` to point to `components/`
259
- 3. Delete `src/islands/` directory
260
-
261
- **Verification**: `ls src/islands/ 2>/dev/null` → directory not found
262
-
263
- **Exit**: No islands/ directory
264
-
265
- See: skill `deco-islands-migration`
266
-
267
- ---
268
-
269
- ### Phase 7 — Section Registry
270
-
271
- **Entry**: Phase 6 complete
272
-
273
- **Actions** (critical — build `src/setup.ts`):
274
-
275
- Modern site setup uses three framework composers + convention-driven section metadata. The old manual registration arrays (`registerSectionsSync`, `setResolvedComponent`, `registerSectionLoaders`, `registerLayoutSections`, `registerSeoSections`, `setAsyncRenderingConfig({ alwaysEager })`) are replaced by codegen reading `export const X = true` declarations from each section file.
276
-
277
- 1. **`createSiteSetup(options)`** from `@decocms/start/setup` — one-call framework bootstrap (sections glob, blocks, meta, CSS, fonts, production origins, preview wrapper, platform init).
278
-
279
- 2. **`applySectionConventions({ meta, syncComponents, loadingFallbacks, sectionGlob })`** from `@decocms/start/cms` — reads `src/server/cms/sections.gen.ts` (produced by `generate-sections.ts`) and registers:
280
- - `eager` → `registerEagerSections`
281
- - `layout` → `registerLayoutSections`
282
- - `seo` → `registerSeoSections`
283
- - `cache` → `registerCacheableSections`
284
- - `sync` → `registerSectionsSync` + bundled sync import
285
- - `hasLoadingFallback` → `registerSection` with fallback
286
- - `clientOnly` → `registerSection` with `clientOnly: true`
287
-
288
- 3. **`autoconfigApps(blocks, APP_REGISTRY)`** from `@decocms/start/apps` — dual-registers every app's loaders + actions into the CMS resolve path AND the admin `/deco/invoke` path from a single source. `APP_REGISTRY` from `@decocms/apps/registry` maintains the list — adding a new app is one entry there, no site code change.
289
-
290
- 4. **`registerCommerceLoaders({ ... })`** — ONLY for site-local loaders that don't belong to an app (e.g., `site/loaders/minicart.ts`, `site/loaders/user.ts`). Do NOT register Shopify/VTEX loaders manually — autoconfig handles those.
291
-
292
- 5. **Section metadata lives in section files**, not in setup.ts:
293
- ```typescript
294
- // src/sections/Header/Header.tsx
295
- export default function Header(props) { /* ... */ }
296
- export const eager = true; // include in alwaysEager
297
- export const sync = true; // bundle sync (no Suspense boundary)
298
- export const layout = true; // render even when CMS page wraps it in Lazy
299
- ```
300
- Then `npm run generate:sections` emits `sections.gen.ts` with the extracted arrays.
301
-
302
- **Template**: `templates/setup-ts.md`
303
-
304
- **Build pipeline** (add to `package.json` scripts):
305
- ```
306
- "generate:blocks": "tsx node_modules/@decocms/start/scripts/generate-blocks.ts",
307
- "generate:schema": "tsx node_modules/@decocms/start/scripts/generate-schema.ts --site <SITE>",
308
- "generate:sections": "tsx node_modules/@decocms/start/scripts/generate-sections.ts",
309
- "generate:loaders": "tsx node_modules/@decocms/start/scripts/generate-loaders.ts",
310
- "generate:invoke": "tsx node_modules/@decocms/start/scripts/generate-invoke.ts",
311
- "generate:routes": "tsr generate",
312
- "build": "npm run generate:blocks && npm run generate:schema && npm run generate:sections && npm run generate:loaders && tsr generate && vite build"
313
- ```
314
-
315
- **Exit**: `setup.ts` compiles; `npm run build` green; every section declared in decofile resolves on SSR.
316
-
317
- See: skill `deco-async-rendering-site-guide`, reference `templates/setup-ts.md`
318
-
319
- ---
320
-
321
- ### Phase 8 — Routes & CMS
322
-
323
- **Entry**: Phase 7 complete
324
-
325
- **Actions**:
326
- 1. Create `src/router.tsx` with scroll restoration
327
- 2. Create `src/routes/__root.tsx` with QueryClient, LiveControls, NavigationProgress, analytics. **Include fallback `description`, `og:site_name`, `og:locale` in root `head()`.** Do NOT include a hardcoded `Device.Provider`.
328
- 3. Create `src/routes/index.tsx` using `cmsHomeRouteConfig({ defaultTitle, defaultDescription, siteName })`
329
- 4. Create `src/routes/$.tsx` using `cmsRouteConfig({ siteName, defaultTitle, defaultDescription })` — spread the full config, do NOT cherry-pick fields. The framework handles SEO head, cache headers, and staleTime/gcTime.
330
- 5. **Create/port `Seo.tsx` component** — must render JSON-LD structured data (NOT return null). Meta tags are handled by the framework's `head()`.
331
- 6. **Port device detection** — use `useSyncExternalStore` + `matchMedia` for client-side. Use `registerSectionLoaders` for server-side UA detection. Do NOT use a hardcoded `Device.Provider`.
332
-
333
- **Templates**: `templates/root-route.md`, `templates/router.md`
334
-
335
- **Exit**: Routes compile, CMS pages resolve, PDPs have JSON-LD + meta description in `<head>`
336
-
337
- See: skills `deco-tanstack-navigation`, `deco-cms-route-config`
338
-
339
- ---
340
-
341
- ### Phase 9 — Worker Entry
342
-
343
- **Entry**: Phase 8 complete
344
-
345
- **Actions**:
346
- 1. Create `src/server.ts` — **CRITICAL: `import "./setup"` MUST be the first line**
347
- 2. Create `src/worker-entry.ts` — same: `import "./setup"` first
348
- 3. Wire admin handlers (handleMeta, handleDecofileRead, handleRender)
349
- 4. Wire VTEX proxy if needed
350
-
351
- **Template**: `templates/worker-entry.md`
352
-
353
- **CRITICAL**: Without `import "./setup"` as the first import, server functions in Vite split modules will have empty state (blocks, registry, commerce loaders). This causes 404 on client-side navigation.
354
-
355
- **Exit**: `npm run dev` serves pages, admin endpoints work
356
-
357
- See: skill `deco-edge-caching`
358
-
359
- ---
360
-
361
- ### Phase 10 — Async Rendering
362
-
363
- **Entry**: Phase 9 complete (site builds and serves pages)
364
-
365
- **Actions**:
366
- 1. Identify lazy sections from CMS Lazy wrappers
367
- 2. Add `export function LoadingFallback()` to lazy sections
368
- 3. Configure `registerCacheableSections()` for SWR on heavy sections
369
- 4. Test deferred section loading on scroll
370
-
371
- **Exit**: Above-the-fold renders instantly, below-fold loads on scroll
372
-
373
- See: skill `deco-async-rendering-site-guide`
374
-
375
- ---
376
-
377
- ## Post-Migration Verification
378
-
379
- ```bash
380
- # 1. Build
381
- npm run build
382
-
383
- # 2. Zero old imports
384
- grep -rE 'from "(preact|@preact|@deco/deco|\$fresh|deco-sites/|apps/)' src/ | wc -l
385
- # Expected: 0
386
-
387
- # 3. Dev server
388
- npm run dev
389
-
390
- # 4. SSR test — load homepage via F5
391
- # 5. Client nav — click links, verify no 404
392
- # 6. Console — no hydration warnings, no missing keys
393
- # 7. Deferred — scroll down, sections load on scroll
394
- # 8. Admin — /deco/meta returns JSON, /live/previews works
395
- ```
396
-
397
- ## Quick Start (Full Migration)
398
-
399
- 1. **Scaffold**: `npm create @tanstack/app@latest -- --template cloudflare-workers`
400
- 2. **Copy source**: Move `src/` from deco-site
401
- 3. **Configure Vite**: See `references/vite-config/`
402
- 4. **Install deps**: `npm install @decocms/start @decocms/apps @tanstack/store @tanstack/react-store`
403
- 5. **Link local libs** (if not published): `cd apps-start && npm link && cd ../deco-start && npm link && cd ../my-store && npm link @decocms/apps @decocms/start`
404
- 6. **Rewrite imports** (parallelizable):
405
- - Preact -> React: `references/imports/`
406
- - Signals -> TanStack Store: `references/signals/`
407
- - Deco framework -> inline: `references/deco-framework/`
408
- - Commerce/widget types -> @decocms/apps + local: `references/commerce/`
409
- - SDK utilities -> packages: `~/sdk/useOffer` -> `@decocms/apps/commerce/sdk/useOffer`, `~/sdk/useScript` -> `@decocms/start/sdk/useScript`, etc.
410
- 7. **Create local UI components**: Image.tsx, Picture.tsx, Seo.tsx, Theme.tsx in `~/components/ui/`
411
- 8. **Implement platform hooks**: `references/platform-hooks/`
412
- 9. **Build & verify**: `npm run build`
413
- 10. **Final audit**: Zero `from "apps/"`, `from "$store/"`, `from "preact"`, `from "@preact"`, `from "@deco/deco"`, `from "~/sdk/useOffer"`, `from "~/sdk/format"` etc. -- all sdk utilities should come from packages
414
-
415
- ## Key Principles
416
-
417
- 1. **No compat layer anywhere** -- not in `@decocms/start`, not in `@decocms/apps`, not in the site repo
418
- 2. **Replace, don't wrap** -- change the import to the real thing, don't create a pass-through
419
- 3. **Types from the library, UI from the site** -- `Product` type comes from `@decocms/apps/commerce/types`, but the `<Image>` component is site-local
420
- 4. **One Vite alias maximum** -- `"~"` -> `"src/"` is the only acceptable alias in a finished migration
421
- 5. **`tsconfig.json` mirrors `vite.config.ts`** -- only `"~/*": ["./src/*"]` in paths
422
- 6. **Signals don't auto-subscribe in React** -- reading `signal.value` in render creates NO subscription; use `useStore(signal.store)` from `@tanstack/react-store`
423
- 7. **Commerce loaders need request context** -- `resolve.ts` must pass URL/path to PLP/PDP loaders for search, categories, sort, and pagination to work
424
- 8. **`wrangler.jsonc` main must be a custom worker-entry** -- TanStack Start ignores `export default` in `server.ts`; create a separate `worker-entry.ts` and point wrangler to it
425
- 9. **Copy components faithfully, never rewrite** -- `cp` the original file, then only change: `class` → `className`, `for` → `htmlFor`, import paths (`apps/` → `~/`, `$store/` → `~/`), `preact` → `react`. NEVER regenerate, "clean up", or "improve" the component. AI-rewritten components are the #1 source of visual regressions -- the layout, grid classes, responsive variants, and conditional logic must be byte-identical to the original except for the mechanical migration changes
426
- 10. **Tailwind v4 logical property hazard** -- mixed `px-*` + `pl-*/pr-*` on the same element breaks the cascade. Replace mixed patterns with consistent longhand (`pl-X pr-X` instead of `px-X`) on those elements only
427
- 11. **oklch CSS variables need triplets, not hex** -- sites using `oklch(var(--x))` must store variables as oklch triplets (`100% 0.00 0deg`), not hex values. `oklch(#FFF)` is invalid CSS
428
- 12. **Verify ALL imports resolve at runtime, not just build** -- Vite tree-shakes dead imports, so `npm run build` passes even with missing modules. But `registerSections` lazy imports execute at runtime, killing entire sections silently
429
- 13. **`import "./setup"` first** — in both `server.ts` and `worker-entry.ts`
430
- 14. **globalThis for split modules** — Vite server function split modules need `globalThis.__deco` to share state
431
-
432
- ## Worker Entry Architecture
433
-
434
- The Cloudflare Worker entry point has a strict layering. Admin routes MUST be handled in `createDecoWorkerEntry` (the outermost wrapper), NOT inside TanStack's `createServerEntry`. TanStack Start's Vite build strips custom logic from `createServerEntry` callbacks in production.
435
-
436
- ```
437
- Request
438
- └─> createDecoWorkerEntry(serverEntry, { admin: { ... } })
439
- ├─> tryAdminRoute() ← FIRST: /live/_meta, /.decofile, /live/previews/*
440
- ├─> cache purge check ← __deco_purge_cache
441
- ├─> static asset bypass ← /assets/*, favicon, sprites
442
- ├─> Cloudflare cache (caches.open)
443
- └─> serverEntry.fetch() ← TanStack Start handles everything else
444
- ```
445
-
446
- ### Site worker-entry.ts Pattern
447
-
448
- ```typescript
449
- import "./setup";
450
- import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
451
- import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
452
- import {
453
- handleMeta, handleDecofileRead, handleDecofileReload,
454
- handleRender, corsHeaders,
455
- } from "@decocms/start/admin";
456
-
457
- const serverEntry = createServerEntry({
458
- async fetch(request) {
459
- return await handler.fetch(request);
460
- },
461
- });
462
-
463
- export default createDecoWorkerEntry(serverEntry, {
464
- admin: { handleMeta, handleDecofileRead, handleDecofileReload, handleRender, corsHeaders },
465
- });
466
- ```
467
-
468
- Key rules:
469
- - `./setup` MUST be imported first (registers sections, loaders, meta, render shell)
470
- - Admin handlers are passed as options, NOT imported inside `createDecoWorkerEntry`
471
- - `/live/` and `/.decofile` are in `DEFAULT_BYPASS_PATHS` -- never cached by the edge
472
-
473
- ### Admin Preview HTML Shell
474
-
475
- The preview at `/live/previews/*` renders sections into an HTML shell. This shell MUST match the production `<html>` attributes for CSS frameworks to work:
476
-
477
- ```typescript
478
- // In setup.ts
479
- setRenderShell({
480
- css: appCss, // Vite ?url import of app.css
481
- fonts: ["https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap"],
482
- theme: "light", // -> <html data-theme="light"> (required for DaisyUI v4)
483
- bodyClass: "bg-base-100 text-base-content",
484
- lang: "pt-BR",
485
- });
486
- ```
487
-
488
- Without `data-theme="light"`, DaisyUI v4 theme variables (`--color-primary`, etc.) won't activate in the preview iframe, causing color mismatches vs production.
489
-
490
- ### Client-Safe vs Server-Only Imports
491
-
492
- `@decocms/start` has two admin entry points:
493
- - **`@decocms/start/admin`** -- server-only handlers (handleMeta, handleRender, etc.) -- these may transitively import `node:async_hooks`
494
- - **`@decocms/start/admin/setup`** (re-exported from `@decocms/start/admin`) -- client-safe setup functions (setMetaData, setInvokeLoaders, setRenderShell) -- NO node: imports
495
-
496
- 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.
497
-
498
- ## Admin Self-Hosting Architecture
499
-
500
- When a site is self-hosted (deployed to its own Cloudflare Worker), the admin communicates with the storefront via the `productionUrl`:
501
-
502
- ```
503
- admin.deco.cx
504
- └─> createContentSiteSDK (when env.platform === "content" OR devContentUrl is set)
505
- ├─> fetch(productionUrl + "/live/_meta") ← schema + manifest
506
- ├─> fetch(productionUrl + "/.decofile") ← content blocks
507
- └─> iframe src = productionUrl + "/live/previews/*" ← section preview
508
- ```
509
-
510
- ### Content URL Resolution Priority
511
-
512
- 1. `devContentUrl` URL param → saved to `localStorage[deco::devContentUrl::${site}]` → used by Content SDK
513
- 2. `devContentUrl` from localStorage → used by Content SDK
514
- 3. `site.metadata.selfHosting.productionUrl` (Supabase) → used by Content SDK
515
- 4. `https://${site}.deco.site` → fallback
516
-
517
- ### Environment Platform Gate
518
-
519
- The admin only uses `createContentSiteSDK` when:
520
- - `devContentUrl` is set (localStorage or URL param), OR
521
- - The current environment has `platform: "content"`
522
-
523
- Setting `productionUrl` in Supabase alone is NOT sufficient. The environment must be "content" platform. This happens when `connectSelfHosting` is called with a `productionUrl` -- it deletes/recreates the staging environment as `platform: "content"`.
524
-
525
- For local dev, use the URL param shortcut:
526
- ```
527
- https://admin.deco.cx/sites/YOUR_SITE/spaces/...?devContentUrl=http://localhost:5181
528
- ```
529
-
530
- ## Admin / CMS Schema Architecture
531
-
532
- The deco admin (deco-cx/deco) communicates with the storefront via:
533
- - `GET /live/_meta` -- returns full JSON Schema + manifest of block types
534
- - `GET /.decofile` -- returns the site's content blocks
535
- - `POST /deco/render` -- renders a section/page with given props in an iframe
536
- - `POST /deco/invoke` -- calls a loader/action and returns JSON
537
-
538
- ### Schema Composition (`composeMeta`)
539
-
540
- The schema generator (`scripts/generate-schema.ts`) only produces section schemas from site TypeScript files. Framework-managed block types (pages) are defined in `src/admin/schema.ts` and injected at runtime via `composeMeta()`.
541
-
542
- ```
543
- [generate-schema.ts] --> meta.gen.json (sections only, pages: empty)
544
- [setup.ts] --> imports meta.gen.json --> calls setMetaData(metaData)
545
- [setMetaData] --> calls composeMeta() --> injects page schema + merges definitions
546
- [/live/_meta] --> returns composed schema with content-hash ETag
547
- ```
548
-
549
- Key rules:
550
- - `toBase64()` MUST produce padded Base64 (matching `btoa()`) -- admin uses `btoa()` to construct definition refs
551
- - Page schema uses flat properties (no allOf + @Props indirection) to minimize RJSF resolution steps
552
- - ETag is a content-based DJB2 hash, not string length, for reliable cache invalidation
553
- - The etag is also included in the JSON response body for admin's `metaInfo.value?.etag` cache check
554
-
555
- ### Admin Local Development
556
-
557
- To use the deco admin with a local storefront:
558
- 1. Start admin: `cd admin && deno task play` (port 4200)
559
- 2. Start storefront: `bun run dev` (port 5181 or wherever it lands)
560
- 3. Set `devContentUrl` in admin's browser console: `localStorage.setItem('deco::devContentUrl::YOUR_SITE_NAME', 'http://localhost:PORT')`
561
- 4. Navigate to `http://localhost:4200/sites/YOUR_SITE_NAME/spaces/pages`
562
- 5. After schema changes: clear admin cache (`localStorage.removeItem('meta::YOUR_SITE_NAME')`) and hard-refresh
563
-
564
- ## Conductor / AI Bulk Migration Workflow
565
-
566
- For sites with 100+ sections and 200+ components, manual file-by-file migration is impractical. The proven workflow:
567
-
568
- ### Phase 1: Scaffold + Copy (human)
569
- 1. Scaffold TanStack Start project
570
- 2. `cp -r` the entire `src/` from the original site
571
- 3. Set up `vite.config.ts`, `tsconfig.json`, `wrangler.jsonc`, `package.json`
572
- 4. Install dependencies
573
-
574
- ### Phase 2: Mechanical Rewrites (AI/conductor)
575
- Let AI tackle the bulk TypeScript errors in a single pass:
576
-
577
- 1. **Import rewrites** (safe for bulk `sed`):
578
- - `from "preact"` → `from "react"`
579
- - `from "preact/hooks"` → `from "react"`
580
- - `from "preact/compat"` → `from "react"`
581
- - `from "@preact/signals"` → `from "~/sdk/signal"`
582
- - `from "apps/commerce/types"` → `from "@decocms/apps/commerce/types"`
583
- - `from "$store/"` → `from "~/"`
584
-
585
- 2. **JSX attribute rewrites** (safe for bulk):
586
- - `class=` → `className=` (in JSX context)
587
- - `for=` → `htmlFor=` (on `<label>` elements)
588
- - `stroke-width` → `strokeWidth`, `fill-rule` → `fillRule` (SVG)
589
- - Remove `data-fresh-disable-lock`
590
-
591
- 3. **Type rewrites** (per-file, AI-assisted):
592
- - `JSX.TargetedEvent<HTMLInputElement>` → `React.ChangeEvent<HTMLInputElement>`
593
- - `JSX.TargetedMouseEvent` → `React.MouseEvent`
594
- - `ComponentChildren` → `ReactNode`
595
- - `SVGAttributes<SVGSVGElement>` → `React.SVGProps<SVGSVGElement>`
596
- - Create consolidated type files (`~/types/vtex.ts`, `~/types/widgets.ts`)
597
-
598
- 4. **Signal-to-state** (per-file, needs judgment):
599
- - `useSignal(x)` → `useState(x)` with setter
600
- - `.value` reads → direct variable reads
601
- - `.value =` writes → `setState()` calls
602
- - Toggle: `x.value = !x.value` → `setX(prev => !prev)`
603
-
604
- ### Phase 3: Verify (human + AI)
605
- 1. `npx tsc --noEmit` — catches remaining type errors
606
- 2. `npm run build` — catches import resolution errors
607
- 3. `bun run dev` + browser test — catches runtime errors
608
- 4. Visual comparison with production — catches layout regressions
609
-
610
- ### Phase 4: Fix Runtime Issues (human-guided)
611
- This is where gotchas 1-45 apply. The mechanical rewrite gets you to "builds clean" but runtime issues require understanding the architectural differences.
612
-
613
- ### Key Insight: Never Rewrite, Only Port
614
-
615
- The conductor approach that worked (836 errors → 0 across 213 files) treated every file as: **copy the original, apply mechanical changes only**. The failed approach was: "look at the original and rewrite it in React" — this produced components that looked similar in code but rendered completely differently because of subtle grid/flex/responsive differences.
616
-
617
- ## Reference Index
618
-
619
- | Topic | Path |
620
- |-------|------|
621
- | Preact → React imports | `references/imports/` |
622
- | Signals → TanStack Store | `references/signals/` |
623
- | Deco framework elimination | `references/deco-framework/` |
624
- | Commerce & widget types | `references/commerce/` |
625
- | Platform hooks (VTEX) | `references/platform-hooks/` |
626
- | Vite configuration | `references/vite-config/` |
627
- | Automation commands | `references/codemod-commands.md` |
628
- | Admin schema composition | `src/admin/schema.ts` in `@decocms/start` |
629
- | Server functions (`createServerFn`) | `references/server-functions/` |
630
- | Common gotchas (45 items) | `references/gotchas.md` |
631
- | Post-migration cleanup checklist | `references/post-migration-cleanup.md` |
632
- | setup.ts template | `templates/setup-ts.md` |
633
- | vite.config.ts template | `templates/vite-config.md` |
634
- | worker-entry template | `templates/worker-entry.md` |
635
- | __root.tsx template | `templates/root-route.md` |
636
- | router.tsx template | `templates/router.md` |
637
- | package.json template | `templates/package-json.md` |
638
-
639
- ## Related Skills
640
-
641
- | Skill | Use When |
642
- |-------|----------|
643
- | deco-apps-vtex-porting | Understanding VTEX loader internals (Phase 4-5) |
644
- | deco-islands-migration | Eliminating islands/ (Phase 6) |
645
- | deco-async-rendering-site-guide | Lazy wrappers, LoadingFallback (Phase 7, 10) |
646
- | deco-cms-route-config | Route config, SEO architecture, device detection (Phase 7-8) |
647
- | deco-tanstack-navigation | Link, prefetch, scroll issues (Phase 8) |
648
- | deco-edge-caching | Worker caching, cache profiles (Phase 9) |
649
- | deco-tanstack-hydration-fixes | Hydration mismatches post-migration |
650
- | deco-tanstack-search | Search page not working |
651
- | deco-typescript-fixes | Bulk TypeScript error resolution |
652
- | deco-start-architecture | Understanding @decocms/start internals |
653
- | deco-tanstack-storefront-patterns | Runtime bugs after migration |
654
- | deco-server-functions-invoke | Server function patterns |
655
- | deco-tanstack-data-flow | Data flow architecture |