@decocms/start 0.39.0 → 0.40.1

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 (34) 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 +1 -1
  33. package/src/routes/cmsRoute.ts +7 -4
  34. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +0 -270
@@ -1,13 +1,278 @@
1
+ # Async Rendering: Architecture & Site Implementation
2
+
3
+ ## Part 1: Architecture
4
+
5
+ Internal documentation for the async section rendering system in `@decocms/start`.
6
+
7
+ ### When to Use This Reference
8
+
9
+ - Debugging why a section is or isn't being deferred
10
+ - Understanding the full request flow from CMS resolution to on-scroll loading
11
+ - Extending the async rendering system (new cache tiers, new deferral strategies)
12
+ - Fixing issues with deferred section data resolution
13
+ - Understanding how bot detection and SEO safety work
14
+ - Working on `@decocms/start` framework code
15
+
16
+ ---
17
+
18
+ ### Problem Solved
19
+
20
+ TanStack Start serializes all `loaderData` as JSON in a `<script>` tag for client-side hydration. When a CMS page has 20+ sections with commerce data, the HTML payload becomes enormous (8+ MB on some pages). The root cause: `resolveDecoPage` fully resolves ALL sections, and TanStack Start embeds everything.
21
+
22
+ ### Architecture Overview
23
+
24
+ ```
25
+ Request → resolveDecoPage()
26
+ ├─ resolveSectionsList() → unwrap flags/blocks to get raw section array
27
+ ├─ shouldDeferSection() → classify each section as eager or deferred
28
+ │ ├─ Eager: resolveRawSection() → full CMS + commerce resolution
29
+ │ └─ Deferred: resolveSectionShallow() → component key + raw CMS props only
30
+ ├─ runSectionLoaders() → enrich eager sections (server loaders)
31
+ └─ Return { resolvedSections, deferredSections }
32
+
33
+ Client render → DecoPageRenderer
34
+ ├─ mergeSections() → interleave eager + deferred by original index
35
+ ├─ Eager: <Suspense><LazyComponent .../></Suspense>
36
+ └─ Deferred: <DeferredSectionWrapper>
37
+ ├─ preloadSectionModule() → get LoadingFallback early
38
+ ├─ Render skeleton (custom LoadingFallback or generic)
39
+ ├─ IntersectionObserver(rootMargin: 300px)
40
+ └─ On intersect: loadDeferredSection serverFn
41
+ ├─ resolveDeferredSection() → resolve __resolveType refs in rawProps
42
+ ├─ runSingleSectionLoader() → enrich with server loader
43
+ └─ Return ResolvedSection → render real component with fade-in
44
+ ```
45
+
46
+ ---
47
+
48
+ ### Deferral Strategy: CMS Lazy.tsx as Source of Truth
49
+
50
+ #### How it works now (respectCmsLazy)
51
+
52
+ The deferral decision is driven by **CMS editor choices**, not a global index threshold:
53
+
54
+ 1. **`respectCmsLazy: true`** (default) — a section is deferred if and only if it's wrapped in `website/sections/Rendering/Lazy.tsx` in the CMS page JSON
55
+ 2. **`foldThreshold`** (default `Infinity`) — fallback for sections NOT wrapped in Lazy; with default `Infinity`, non-wrapped sections are always eager
56
+ 3. **`alwaysEager`** — section keys that override all deferral (Header, Footer, Theme, etc.)
57
+
58
+ #### Why this approach
59
+
60
+ The previous `foldThreshold` approach deferred sections by index position, ignoring editor intent. This caused:
61
+ - Sections that editors wanted eager getting deferred
62
+ - No control per-page (threshold was global)
63
+ - Homepage with 12 sections marked Lazy in CMS showing 0 deferred
64
+
65
+ Now editors control deferral by wrapping sections in `Lazy.tsx` in the CMS admin, and the framework respects that.
66
+
67
+ #### `isCmsLazyWrapped(section)` in `resolve.ts`
68
+
69
+ Detects whether a section is wrapped in `website/sections/Rendering/Lazy.tsx`, either:
70
+ - Directly: `section.__resolveType === "website/sections/Rendering/Lazy.tsx"`
71
+ - Via named block: `section.__resolveType` references a block whose `__resolveType` is `"website/sections/Rendering/Lazy.tsx"`
72
+
73
+ #### `shouldDeferSection(section, flatIndex, cfg, isBotReq)`
74
+
75
+ Updated decision logic:
76
+
77
+ ```
78
+ 1. Bot request? → EAGER (SEO safety)
79
+ 2. No __resolveType? → EAGER (can't classify)
80
+ 3. Is multivariate flag? → EAGER (requires runtime evaluation)
81
+ 4. resolveFinalSectionKey() → walk block refs + Lazy wrappers to find final component
82
+ 5. In alwaysEager set? → EAGER
83
+ 6. isLayoutSection()? → EAGER
84
+ 7. respectCmsLazy && isCmsLazyWrapped(section)? → DEFER
85
+ 8. flatIndex >= foldThreshold? → DEFER (fallback, only if not wrapped)
86
+ 9. Otherwise → EAGER
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Files and Their Roles
92
+
93
+ | File | Layer | Role |
94
+ |------|-------|------|
95
+ | `src/cms/resolve.ts` | Server | Types, config, eager/deferred split, CMS Lazy detection, shallow resolution, full deferred resolution |
96
+ | `src/cms/sectionLoaders.ts` | Server | Section loader registry, layout cache, SWR cacheable sections, `runSingleSectionLoader` |
97
+ | `src/cms/registry.ts` | Shared | Section component registry, `preloadSectionModule` for early LoadingFallback |
98
+ | `src/routes/cmsRoute.ts` | Server | `loadCmsPage`, `loadCmsHomePage`, `loadDeferredSection` server functions |
99
+ | `src/hooks/DecoPageRenderer.tsx` | Client | Merge, render eager/deferred, `DeferredSectionWrapper`, dev warnings |
100
+ | `src/cms/index.ts` | Barrel | Re-exports all public types and functions |
101
+ | `src/routes/index.ts` | Barrel | Re-exports route helpers including `loadDeferredSection` |
102
+
103
+ ---
104
+
105
+ ### Server-Side: Eager/Deferred Split
106
+
107
+ #### Entry point: `resolveDecoPage()` in `resolve.ts`
108
+
109
+ ```
110
+ resolveDecoPage(targetPath, matcherCtx)
111
+ 1. findPageByPath(targetPath) → { page, params }
112
+ 2. Get raw sections array:
113
+ - If page.sections is Array → use directly
114
+ - If page.sections is wrapped (multivariate flag, block ref) → resolveSectionsList()
115
+ 3. For each raw section:
116
+ - If shouldDeferSection() → resolveSectionShallow() → DeferredSection
117
+ - Else → resolveRawSection() (full resolution) → ResolvedSection[]
118
+ 4. Return { resolvedSections, deferredSections }
119
+ ```
120
+
121
+ #### `resolveSectionsList(value, rctx, depth)`
122
+
123
+ Resolves **only the outer wrapper** around the sections array. Handles multivariate flags, named block references, and `resolved` type wrappers. Extracts the raw section array WITHOUT resolving individual section commerce loaders.
124
+
125
+ #### `resolveFinalSectionKey(section)`
126
+
127
+ Walks block reference chain and unwraps `Lazy` wrappers to find the final registered section component key:
128
+
129
+ ```
130
+ "Header - 01" (named block)
131
+ → { __resolveType: "website/sections/Rendering/Lazy.tsx", section: {...} }
132
+ → { __resolveType: "site/sections/Header/Header.tsx", ...props }
133
+ ```
134
+
135
+ Returns `"site/sections/Header/Header.tsx"`, checked against `alwaysEager` and `isLayoutSection`.
136
+
137
+ #### `resolveSectionShallow(section)`
138
+
139
+ Synchronously follows block refs and unwraps Lazy to extract `component` (final key) and `rawProps` (CMS props as-is). No API calls, no async.
140
+
141
+ #### `resolveDeferredSection(component, rawProps, pagePath, matcherCtx)`
142
+
143
+ Called when client requests a deferred section. Runs full resolution:
144
+ 1. `resolveProps(rawProps, rctx)` — resolves all nested `__resolveType` references
145
+ 2. `normalizeNestedSections(resolvedProps)` — converts nested sections to `{ Component, props }`
146
+ 3. Returns `ResolvedSection` ready for `runSingleSectionLoader`
147
+
1
148
  ---
2
- name: deco-async-rendering-site-guide
3
- description: Site-level guide for implementing Async Section Rendering in Deco storefronts on TanStack Start. Covers LoadingFallback implementation with detailed product card skeletons, setup.ts configuration using CMS Lazy.tsx wrappers (respectCmsLazy), adding Lazy wrappers to CMS page JSONs, route wiring ($.tsx, index.tsx), alwaysEager sections, NavigationProgress for SPA transitions, and diagnosing dev warnings. Use when adding async rendering to a new Deco site, creating LoadingFallback components, wrapping CMS sections in Lazy, debugging deferred sections, or optimizing page payload.
149
+
150
+ ### Server-Side: Section Caching
151
+
152
+ #### Three cache tiers in `sectionLoaders.ts`
153
+
154
+ **Tier 1: Layout sections** (Header, Footer, Theme)
155
+ - 5-minute TTL, in-flight dedup, registered via `registerLayoutSections`
156
+
157
+ **Tier 2: Cacheable sections** (ProductShelf, FAQ)
158
+ - Configurable TTL via `registerCacheableSections`, SWR semantics, LRU eviction at 200 entries
159
+ - Cache key: `component::djb2Hash(JSON.stringify(props))`
160
+
161
+ **Tier 3: Regular sections** — No caching, always fresh.
162
+
163
+ ---
164
+
165
+ ### Client-Side: DeferredSectionWrapper
166
+
167
+ #### Lifecycle
168
+
169
+ ```
170
+ 1. Mount (stableKey = pagePath + component + index)
171
+ ├─ preloadSectionModule(component) → extract LoadingFallback
172
+ └─ Render skeleton (custom or generic DefaultSectionFallback)
173
+
174
+ 2. IntersectionObserver (rootMargin: "300px")
175
+ └─ On intersect (once):
176
+ ├─ loadDeferredSection serverFn
177
+ ├─ On success: render <LazyComponent .../> with fade-in
178
+ └─ On error: render ErrorFallback or null
179
+
180
+ 3. SPA navigation: stableKey changes → reset state (triggered, section, error)
181
+ ```
182
+
183
+ #### Key: stableKey for SPA navigation
184
+
185
+ `DeferredSectionWrapper` uses `pagePath + component + index` as a stable key. When the route changes, this key changes, forcing React to remount the wrapper and reset all internal state. This prevents deferred sections from a previous page being "stuck" in a triggered state.
186
+
187
+ ---
188
+
189
+ ### Bot Detection (SEO Safety)
190
+
191
+ `isBot(userAgent)` regex detects search engine crawlers. When detected, ALL sections are resolved eagerly — `deferredSections` is empty.
192
+
193
+ ---
194
+
195
+ ### Types
196
+
197
+ #### `AsyncRenderingConfig`
198
+
199
+ ```ts
200
+ interface AsyncRenderingConfig {
201
+ respectCmsLazy: boolean; // Default true — use Lazy.tsx wrappers as deferral source
202
+ foldThreshold: number; // Default Infinity — fallback for non-wrapped sections
203
+ alwaysEager: Set<string>; // Section keys that must always be eager
204
+ }
205
+ ```
206
+
207
+ #### `DeferredSection`
208
+
209
+ ```ts
210
+ interface DeferredSection {
211
+ component: string;
212
+ key: string;
213
+ index: number;
214
+ rawProps: Record<string, unknown>;
215
+ }
216
+ ```
217
+
218
+ ---
219
+
220
+ ### Edge Cases and Gotchas
221
+
222
+ #### 1. CMS Lazy.tsx is the source of truth
223
+ Editors wrap sections in `website/sections/Rendering/Lazy.tsx` in the CMS admin. The framework detects this via `isCmsLazyWrapped()` and defers those sections. Sections NOT wrapped are eager (with `foldThreshold: Infinity`).
224
+
225
+ #### 2. Block references to Lazy
226
+ A section may reference a named block (e.g., `"Footer - 01"`) whose underlying definition is `Lazy.tsx`. `isCmsLazyWrapped` resolves one level of block reference to detect this.
227
+
228
+ #### 3. alwaysEager overrides Lazy wrapping
229
+ If `Footer.tsx` is in `alwaysEager` but wrapped in Lazy in the CMS, it stays eager. This is intentional — layout sections must always be in the initial HTML.
230
+
231
+ #### 4. Multivariate flags are always eager
232
+ Individual sections wrapped in `website/flags/multivariate.ts` require runtime matcher evaluation and can't be safely deferred.
233
+
234
+ #### 5. InvalidCharacterError with section rendering
235
+ In TanStack Start, resolved sections have `Component` as a string key (not a React component). Use `SectionRenderer` or `SectionList` from `@decocms/start/hooks` to render sections — never destructure `{ Component, props }` and use as JSX directly.
236
+
237
+ #### 6. Navigation flash prevention
238
+ Don't use `pendingComponent` on CMS routes — it replaces the entire page content (including Header/Footer) during transitions. Instead, use a root-level `NavigationProgress` bar that keeps previous page visible while loading.
239
+
240
+ ---
241
+
242
+ ### Public API Summary
243
+
244
+ #### From `@decocms/start/cms`
245
+
246
+ | Export | Type | Description |
247
+ |--------|------|-------------|
248
+ | `setAsyncRenderingConfig` | Function | Enable/configure async rendering |
249
+ | `getAsyncRenderingConfig` | Function | Read current config |
250
+ | `registerCacheableSections` | Function | Register sections for SWR loader caching |
251
+ | `runSingleSectionLoader` | Function | Run a single section's loader |
252
+ | `resolveDeferredSection` | Function | Fully resolve a deferred section's raw props |
253
+ | `preloadSectionModule` | Function | Eagerly import a section to extract LoadingFallback |
254
+
255
+ #### From `@decocms/start/routes`
256
+
257
+ | Export | Type | Description |
258
+ |--------|------|-------------|
259
+ | `loadDeferredSection` | ServerFn | Server function to resolve + enrich deferred section on demand |
260
+
261
+ #### From `@decocms/start/hooks`
262
+
263
+ | Export | Type | Description |
264
+ |--------|------|-------------|
265
+ | `DecoPageRenderer` | Component | Renders page with eager + deferred section support |
266
+ | `SectionRenderer` | Component | Renders a single section by registry key |
267
+ | `SectionList` | Component | Renders an array of sections |
268
+
4
269
  ---
5
270
 
6
- # Deco Async Section Rendering — Site Implementation Guide
271
+ ## Part 2: Site Implementation
7
272
 
8
273
  How to configure and use Async Section Rendering in your Deco storefront.
9
274
 
10
- ## When to Use This Skill
275
+ ### When to Use This Reference
11
276
 
12
277
  - Setting up async section rendering on a new or existing Deco site
13
278
  - Creating `LoadingFallback` components for sections
@@ -18,9 +283,9 @@ How to configure and use Async Section Rendering in your Deco storefront.
18
283
 
19
284
  ---
20
285
 
21
- ## Quick Start (3 steps)
286
+ ### Quick Start (3 steps)
22
287
 
23
- ### 1. `src/setup.ts` — Enable async rendering
288
+ #### 1. `src/setup.ts` — Enable async rendering
24
289
 
25
290
  ```ts
26
291
  import {
@@ -49,7 +314,7 @@ registerCacheableSections({
49
314
  });
50
315
  ```
51
316
 
52
- ### 2. Wrap sections in Lazy in CMS JSONs
317
+ #### 2. Wrap sections in Lazy in CMS JSONs
53
318
 
54
319
  In `.deco/blocks/pages-*.json`, wrap below-the-fold sections:
55
320
 
@@ -75,15 +340,15 @@ In `.deco/blocks/pages-*.json`, wrap below-the-fold sections:
75
340
  - SEO sections → **skip** (they're metadata, not visual)
76
341
  - Everything else below the fold → **wrap in Lazy**
77
342
 
78
- ### 3. Add LoadingFallback to every lazy section
343
+ #### 3. Add LoadingFallback to every lazy section
79
344
 
80
345
  Export `LoadingFallback` from the section file. See detailed patterns below.
81
346
 
82
347
  ---
83
348
 
84
- ## CMS Lazy Wrapper Strategy
349
+ ### CMS Lazy Wrapper Strategy
85
350
 
86
- ### Page audit checklist
351
+ #### Page audit checklist
87
352
 
88
353
  For each CMS page (`pages-*.json`):
89
354
 
@@ -92,7 +357,7 @@ For each CMS page (`pages-*.json`):
92
357
  3. Wrap everything else in `website/sections/Rendering/Lazy.tsx`.
93
358
  4. Keep `alwaysEager` sections (Header, Footer, etc.) unwrapped even if they appear at the end.
94
359
 
95
- ### Real-world example: Homepage
360
+ #### Real-world example: Homepage
96
361
 
97
362
  | Index | Section | Status |
98
363
  |-------|---------|--------|
@@ -111,9 +376,9 @@ Result: 4 eager + 17 lazy → **52% payload reduction**.
111
376
 
112
377
  ---
113
378
 
114
- ## Creating LoadingFallback Components
379
+ ### Creating LoadingFallback Components
115
380
 
116
- ### Key rules
381
+ #### Key rules
117
382
 
118
383
  1. **Match dimensions**: Same container classes, padding, and aspect ratio as the real section
119
384
  2. **CSS-only**: Use `skeleton animate-pulse` classes. No JS, no hooks, no data.
@@ -121,7 +386,7 @@ Result: 4 eager + 17 lazy → **52% payload reduction**.
121
386
  4. **One per section file**: Export from `src/sections/Foo.tsx`, not from the component file
122
387
  5. **Represent the content**: Skeletons should visually match the final layout
123
388
 
124
- ### Product Card Skeleton (reusable pattern)
389
+ #### Product Card Skeleton (reusable pattern)
125
390
 
126
391
  Most shelf/grid sections contain product cards. Define a shared skeleton:
127
392
 
@@ -148,7 +413,7 @@ function CardSkeleton() {
148
413
 
149
414
  This matches the real `ProductCard` layout: image → flag → name (2 lines) → price block (from/to/installment) → buy button.
150
415
 
151
- ### Pattern: Product Shelf
416
+ #### Pattern: Product Shelf
152
417
 
153
418
  ```tsx
154
419
  export function LoadingFallback() {
@@ -168,7 +433,7 @@ export function LoadingFallback() {
168
433
  }
169
434
  ```
170
435
 
171
- ### Pattern: Tabbed Shelf
436
+ #### Pattern: Tabbed Shelf
172
437
 
173
438
  ```tsx
174
439
  export function LoadingFallback() {
@@ -194,7 +459,7 @@ export function LoadingFallback() {
194
459
  }
195
460
  ```
196
461
 
197
- ### Pattern: Search Result (PLP)
462
+ #### Pattern: Search Result (PLP)
198
463
 
199
464
  ```tsx
200
465
  export function LoadingFallback() {
@@ -232,7 +497,7 @@ export function LoadingFallback() {
232
497
  }
233
498
  ```
234
499
 
235
- ### Pattern: Full-width Banner/Carousel
500
+ #### Pattern: Full-width Banner/Carousel
236
501
 
237
502
  ```tsx
238
503
  export function LoadingFallback() {
@@ -244,7 +509,7 @@ export function LoadingFallback() {
244
509
  }
245
510
  ```
246
511
 
247
- ### Pattern: FAQ Accordion
512
+ #### Pattern: FAQ Accordion
248
513
 
249
514
  ```tsx
250
515
  export function LoadingFallback() {
@@ -260,7 +525,7 @@ export function LoadingFallback() {
260
525
  }
261
526
  ```
262
527
 
263
- ### Pattern: Testimonials/Cards Grid
528
+ #### Pattern: Testimonials/Cards Grid
264
529
 
265
530
  ```tsx
266
531
  export function LoadingFallback() {
@@ -282,7 +547,7 @@ export function LoadingFallback() {
282
547
  }
283
548
  ```
284
549
 
285
- ### Pattern: Footer
550
+ #### Pattern: Footer
286
551
 
287
552
  ```tsx
288
553
  export function LoadingFallback() {
@@ -308,7 +573,7 @@ export function LoadingFallback() {
308
573
 
309
574
  ---
310
575
 
311
- ## SPA Navigation: NavigationProgress
576
+ ### SPA Navigation: NavigationProgress
312
577
 
313
578
  **Do NOT use `pendingComponent`** on CMS routes — it replaces the entire page content (Header/Footer disappear, causing a "flash white").
314
579
 
@@ -338,9 +603,9 @@ Add `<NavigationProgress />` before your main layout in `RootLayout`.
338
603
 
339
604
  ---
340
605
 
341
- ## Configuration Reference
606
+ ### Configuration Reference
342
607
 
343
- ### `setAsyncRenderingConfig(options)`
608
+ #### `setAsyncRenderingConfig(options)`
344
609
 
345
610
  | Option | Type | Default | Description |
346
611
  |--------|------|---------|-------------|
@@ -348,7 +613,7 @@ Add `<NavigationProgress />` before your main layout in `RootLayout`.
348
613
  | `foldThreshold` | `number` | `Infinity` | Fallback for non-wrapped sections (Infinity = only Lazy-wrapped defer) |
349
614
  | `alwaysEager` | `string[]` | `[]` | Section keys that are ALWAYS eager regardless |
350
615
 
351
- ### `registerCacheableSections(configs)`
616
+ #### `registerCacheableSections(configs)`
352
617
 
353
618
  ```ts
354
619
  registerCacheableSections({
@@ -360,9 +625,9 @@ Good candidates: Product shelves (2-3 min), FAQ/content (15-30 min). NOT for PDP
360
625
 
361
626
  ---
362
627
 
363
- ## Debugging
628
+ ### Debugging
364
629
 
365
- ### Section not being deferred
630
+ #### Section not being deferred
366
631
 
367
632
  1. Is `setAsyncRenderingConfig()` called in `setup.ts`?
368
633
  2. Is the section wrapped in `website/sections/Rendering/Lazy.tsx` in the CMS JSON?
@@ -371,7 +636,7 @@ Good candidates: Product shelves (2-3 min), FAQ/content (15-30 min). NOT for PDP
371
636
  5. Is it wrapped in a multivariate flag? (always eager)
372
637
  6. Is the user-agent a bot? (bots always get full eager)
373
638
 
374
- ### Verifying with curl
639
+ #### Verifying with curl
375
640
 
376
641
  ```bash
377
642
  # Normal user — count deferred sections
@@ -385,13 +650,13 @@ curl -s -o /dev/null -w "Normal: %{size_download}\n" http://localhost:5173/
385
650
  curl -s -o /dev/null -w "Bot: %{size_download}\n" -A "Googlebot/2.1" http://localhost:5173/
386
651
  ```
387
652
 
388
- ### InvalidCharacterError with sections
653
+ #### InvalidCharacterError with sections
389
654
 
390
655
  If you see `Failed to execute 'createElement'` with a section path as tag name, the component is using `{ Component, props }` destructuring directly as JSX. Use `SectionRenderer` or `SectionList` from `@decocms/start/hooks` instead.
391
656
 
392
657
  ---
393
658
 
394
- ## Performance Impact
659
+ ### Performance Impact
395
660
 
396
661
  Measured on `espacosmart-storefront`:
397
662
 
@@ -403,7 +668,7 @@ Measured on `espacosmart-storefront`:
403
668
 
404
669
  ---
405
670
 
406
- ## Checklist for New Sites
671
+ ### Checklist for New Sites
407
672
 
408
673
  - [ ] Call `setAsyncRenderingConfig()` in `setup.ts` with `alwaysEager` sections
409
674
  - [ ] Audit all CMS page JSONs — wrap below-fold sections in `Lazy.tsx`
@@ -0,0 +1,174 @@
1
+ # Codemod Commands
2
+
3
+ All automation commands organized by phase. Run from project root.
4
+
5
+ ## Phase 1 — Imports & JSX
6
+
7
+ ### Preact → React (safe for bulk)
8
+
9
+ ```bash
10
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
11
+ -e 's|from "preact/hooks"|from "react"|g' \
12
+ -e 's|from "preact/compat"|from "react"|g' \
13
+ -e 's|from "preact"|from "react"|g'
14
+ ```
15
+
16
+ ### ComponentChildren → ReactNode
17
+
18
+ ```bash
19
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
20
+ -e 's/ComponentChildren/ReactNode/g'
21
+ ```
22
+
23
+ ### SVG attributes (safe for bulk)
24
+
25
+ ```bash
26
+ find src/ -name '*.tsx' | xargs sed -i '' \
27
+ -e 's/stroke-width=/strokeWidth=/g' \
28
+ -e 's/stroke-linecap=/strokeLinecap=/g' \
29
+ -e 's/stroke-linejoin=/strokeLinejoin=/g' \
30
+ -e 's/fill-rule=/fillRule=/g' \
31
+ -e 's/clip-rule=/clipRule=/g' \
32
+ -e 's/clip-path=/clipPath=/g' \
33
+ -e 's/stroke-dasharray=/strokeDasharray=/g' \
34
+ -e 's/stroke-dashoffset=/strokeDashoffset=/g'
35
+ ```
36
+
37
+ ### HTML attributes
38
+
39
+ ```bash
40
+ find src/ -name '*.tsx' | xargs sed -i '' \
41
+ -e 's/ for=/ htmlFor=/g' \
42
+ -e 's/ fetchpriority=/ fetchPriority=/g' \
43
+ -e 's/ autocomplete=/ autoComplete=/g'
44
+ ```
45
+
46
+ ### Remove JSX pragmas
47
+
48
+ ```bash
49
+ find src/ -name '*.tsx' -o -name '*.ts' | xargs sed -i '' \
50
+ -e '/\/\*\* @jsxRuntime automatic \*\//d' \
51
+ -e '/\/\*\* @jsx h \*\//d' \
52
+ -e '/\/\*\* @jsxFrag Fragment \*\//d'
53
+ ```
54
+
55
+ ## Phase 2 — Signals
56
+
57
+ ### Module-level signal imports (safe for bulk)
58
+
59
+ ```bash
60
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
61
+ 's|from "@preact/signals"|from "@decocms/start/sdk/signal"|g'
62
+ ```
63
+
64
+ ### Audit useSignal usage (manual conversion needed)
65
+
66
+ ```bash
67
+ grep -rn 'useSignal\|useComputed' src/ --include='*.tsx' --include='*.ts'
68
+ ```
69
+
70
+ ## Phase 3 — Deco Framework
71
+
72
+ ### Remove $fresh imports
73
+
74
+ ```bash
75
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
76
+ -e 's|import { asset } from "\$fresh/runtime.ts";||g' \
77
+ -e 's|asset(\([^)]*\))|\1|g'
78
+ ```
79
+
80
+ ### Replace site-local import aliases
81
+
82
+ ```bash
83
+ # Replace with your actual site name:
84
+ SITE_NAME="osklenbr"
85
+
86
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
87
+ -e "s|from \"\\\$store/|from \"~/|g" \
88
+ -e "s|from \"deco-sites/${SITE_NAME}/|from \"~/|g" \
89
+ -e "s|from \"site/|from \"~/|g"
90
+ ```
91
+
92
+ ### IS_BROWSER replacement
93
+
94
+ ```bash
95
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
96
+ -e 's|import { IS_BROWSER } from "\$fresh/runtime.ts";||g' \
97
+ -e 's|IS_BROWSER|typeof window !== "undefined"|g'
98
+ ```
99
+
100
+ ## Phase 4 — Commerce
101
+
102
+ ```bash
103
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
104
+ -e 's|from "apps/commerce/types.ts"|from "@decocms/apps/commerce/types"|g' \
105
+ -e 's|from "apps/admin/widgets.ts"|from "~/types/widgets"|g' \
106
+ -e 's|from "apps/website/components/Image.tsx"|from "~/components/ui/Image"|g' \
107
+ -e 's|from "apps/website/components/Picture.tsx"|from "~/components/ui/Picture"|g' \
108
+ -e 's|from "apps/website/components/Video.tsx"|from "~/components/ui/Video"|g'
109
+ ```
110
+
111
+ ### SDK utilities
112
+
113
+ ```bash
114
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
115
+ -e 's|from "~/sdk/useOffer.ts"|from "@decocms/apps/commerce/sdk/useOffer"|g' \
116
+ -e 's|from "~/sdk/useOffer"|from "@decocms/apps/commerce/sdk/useOffer"|g' \
117
+ -e 's|from "~/sdk/format.ts"|from "@decocms/apps/commerce/sdk/formatPrice"|g'
118
+ ```
119
+
120
+ ## Phase 6 — Islands
121
+
122
+ ### Audit island types
123
+
124
+ ```bash
125
+ echo "=== Wrapper islands (re-export from components) ==="
126
+ grep -rl 'export.*from.*components' src/islands/ --include='*.tsx' 2>/dev/null
127
+
128
+ echo ""
129
+ echo "=== Standalone islands (have real logic) ==="
130
+ find src/islands/ -name '*.tsx' ! -exec grep -l 'export.*from.*components' {} \; 2>/dev/null
131
+ ```
132
+
133
+ ### Repoint imports from islands/ to components/
134
+
135
+ ```bash
136
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
137
+ 's|from "~/islands/|from "~/components/|g'
138
+ ```
139
+
140
+ ## Verification Commands
141
+
142
+ ```bash
143
+ # Zero old imports (run after all phases):
144
+ echo "Preact: $(grep -r 'from "preact' src/ --include='*.tsx' --include='*.ts' | wc -l)"
145
+ echo "Signals: $(grep -r '@preact/signals' src/ --include='*.tsx' --include='*.ts' | wc -l)"
146
+ echo "@deco/deco: $(grep -r '@deco/deco' src/ --include='*.tsx' --include='*.ts' | wc -l)"
147
+ echo "\$fresh: $(grep -r '\$fresh' src/ --include='*.tsx' --include='*.ts' | wc -l)"
148
+ echo "apps/: $(grep -r 'from \"apps/' src/ --include='*.tsx' --include='*.ts' | wc -l)"
149
+ echo "islands/: $(grep -r 'from \"~/islands/' src/ --include='*.tsx' --include='*.ts' | wc -l)"
150
+ ```
151
+
152
+ ## Pre-Flight Audit Script
153
+
154
+ Run against the source site before starting migration:
155
+
156
+ ```bash
157
+ echo "=== Source Site Audit ==="
158
+ echo "Components: $(find components/ sections/ -name '*.tsx' 2>/dev/null | wc -l)"
159
+ echo "Islands: $(find islands/ -name '*.tsx' 2>/dev/null | wc -l)"
160
+ echo "Sections: $(find sections/ -name '*.tsx' 2>/dev/null | wc -l)"
161
+ echo "Loaders: $(find loaders/ -name '*.ts' -o -name '*.tsx' 2>/dev/null | wc -l)"
162
+ echo ""
163
+ echo "=== Import Dependencies ==="
164
+ echo "Preact: $(grep -rl 'from "preact' . --include='*.tsx' --include='*.ts' 2>/dev/null | wc -l) files"
165
+ echo "Signals: $(grep -rl '@preact/signals' . --include='*.tsx' --include='*.ts' 2>/dev/null | wc -l) files"
166
+ echo "@deco/deco: $(grep -rl '@deco/deco' . --include='*.tsx' --include='*.ts' 2>/dev/null | wc -l) files"
167
+ echo "\$fresh: $(grep -rl '\$fresh/' . --include='*.tsx' --include='*.ts' 2>/dev/null | wc -l) files"
168
+ echo "apps/: $(grep -rl 'from \"apps/' . --include='*.tsx' --include='*.ts' 2>/dev/null | wc -l) files"
169
+ echo "useSignal: $(grep -r 'useSignal' . --include='*.tsx' --include='*.ts' -c 2>/dev/null | awk -F: '{sum+=$2} END{print sum}')"
170
+ echo ""
171
+ echo "=== CMS Blocks ==="
172
+ echo "Total: $(find .deco/blocks/ -name '*.json' 2>/dev/null | wc -l)"
173
+ echo "Pages: $(find .deco/blocks/ -name 'pages-*.json' 2>/dev/null | wc -l)"
174
+ ```