@decocms/start 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: deco-apps-vtex-review
3
+ description: Audit and fix the VTEX integration in @decocms/apps-start (TanStack Start). Covers cookie propagation (vtexFetchWithCookies, buildAuthCookieHeader), expectedOrderFormSections, salesChannel injection, HttpOnly cookie handling, Intelligent Search cookie generation, useCart/useUser/useWishlist hooks, and TypeScript validation. Use when reviewing vtex/ code quality, fixing authentication issues, debugging missing cart sections, or ensuring full parity with deco-cx/apps.
4
+ ---
5
+
6
+ # VTEX apps-start Review & Fix
7
+
8
+ Comprehensive audit checklist for the VTEX integration in `@decocms/apps-start`. Use after porting or when debugging issues.
9
+
10
+ ## File Structure
11
+
12
+ ```
13
+ apps-start/vtex/
14
+ ├── client.ts # vtexFetch, vtexFetchWithCookies, intelligentSearch, vtexIOGraphQL
15
+ ├── middleware.ts # extractVtexContext, propagateISCookies
16
+ ├── actions/
17
+ │ ├── checkout.ts # Cart mutations (addItems, updateItems, etc.)
18
+ │ ├── auth.ts # classicSignIn, logout, sendEmailVerification
19
+ │ ├── session.ts # createSession, editSession, deleteSession
20
+ │ ├── address.ts # GraphQL address mutations
21
+ │ ├── misc.ts # notifyMe, sendEvent, submitReview, deletePaymentToken
22
+ │ ├── newsletter.ts # subscribe, updateNewsletterOptIn
23
+ │ ├── orders.ts # cancelOrder
24
+ │ ├── profile.ts # updateProfile, updateAddress
25
+ │ ├── wishlist.ts # addItem, removeItem
26
+ │ └── trigger.ts # Analytics trigger
27
+ ├── loaders/
28
+ │ ├── cart.ts # getCart (OrderForm)
29
+ │ ├── catalog.ts # searchProducts, getCrossSelling, getCategoryTree
30
+ │ ├── legacy.ts # legacyProductDetailsPage, legacyProductList, legacyPLP, legacySuggestions
31
+ │ ├── workflow.ts # workflowProduct, workflowProducts
32
+ │ ├── search.ts # getTopSearches, getProductIdByTerm
33
+ │ └── (14 more)
34
+ ├── inline-loaders/ # TanStack-compatible loaders for sections
35
+ ├── hooks/ # Client-side React hooks (useCart, useUser, useWishlist)
36
+ └── utils/
37
+ ├── transform.ts # Canonical VTEX→schema.org mapping
38
+ ├── types.ts # VTEX API types
39
+ ├── vtexId.ts # VTEX_AUTH_COOKIE, buildAuthCookieHeader
40
+ ├── segment.ts # buildSegmentFromCookies, isAnonymous
41
+ ├── intelligentSearch.ts # withDefaultParams, withDefaultFacets
42
+ ├── similars.ts # withIsSimilarTo
43
+ └── enrichment.ts # withSimulation
44
+ ```
45
+
46
+ ## Audit Checklist
47
+
48
+ ### 1. Cookie Propagation
49
+
50
+ VTEX APIs return `Set-Cookie` headers that must reach the browser. Standard `vtexFetch` discards them.
51
+
52
+ **Pattern**: Use `vtexFetchWithCookies` for any action that creates/modifies server state:
53
+
54
+ ```typescript
55
+ import { vtexFetchWithCookies } from "../client";
56
+ import type { VtexFetchResult } from "../client";
57
+
58
+ // Returns { data: T, setCookies: string[] }
59
+ const result = await vtexFetchWithCookies<OrderForm>(url, opts);
60
+ ```
61
+
62
+ **Where required**: `checkout.ts` (all cart mutations), `session.ts` (create/edit), `auth.ts` (signIn, logout).
63
+
64
+ **Where NOT needed**: Read-only loaders, GraphQL queries.
65
+
66
+ ### 2. Auth Cookie Headers
67
+
68
+ All authenticated VTEX IO GraphQL calls need both cookie variants:
69
+
70
+ ```
71
+ VtexIdclientAutCookie={token}; VtexIdclientAutCookie_{account}={token}
72
+ ```
73
+
74
+ **Use the centralized helper**:
75
+
76
+ ```typescript
77
+ import { buildAuthCookieHeader, VTEX_AUTH_COOKIE } from "../utils/vtexId";
78
+ import { getVtexConfig } from "../client";
79
+
80
+ const { account } = getVtexConfig();
81
+ const cookieHeader = buildAuthCookieHeader(authCookie, account);
82
+ // Pass as: { cookie: cookieHeader } or { Cookie: cookieHeader }
83
+ ```
84
+
85
+ **Audit**: grep for hardcoded `VtexIdclientAutCookie` strings. Only `vtexId.ts` should define it.
86
+
87
+ ```bash
88
+ rg "VtexIdclientAutCookie" vtex/ --glob '!vtex/utils/vtexId.ts'
89
+ ```
90
+
91
+ Any match outside `vtexId.ts` (except JSDoc comments) is a bug.
92
+
93
+ ### 3. expectedOrderFormSections
94
+
95
+ VTEX Checkout API returns incomplete OrderForm without explicit sections. Every POST to `/api/checkout/pub/orderForm` must include:
96
+
97
+ ```typescript
98
+ import { DEFAULT_EXPECTED_SECTIONS } from "../actions/checkout";
99
+
100
+ body: JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS })
101
+ ```
102
+
103
+ **Audit**: Check `loaders/cart.ts` and `hooks/useCart.ts` — both must send this body.
104
+
105
+ ### 4. salesChannel (sc) Parameter
106
+
107
+ Missing `sc` causes wrong prices, ORD027, or invisible products.
108
+
109
+ **Where required**:
110
+ - All `/api/checkout/pub/orderForm/*` endpoints → `?sc={sc}`
111
+ - `/api/catalog_system/pub/products/search/*` → `?sc={sc}`
112
+ - `/buscaautocomplete` → `&sc={sc}`
113
+ - Intelligent Search: handled by `client.ts` `intelligentSearch()` automatically
114
+
115
+ **Audit**:
116
+
117
+ ```bash
118
+ rg "catalog_system/pub/products/search|buscaautocomplete|orderForm" vtex/ | rg -v "sc="
119
+ ```
120
+
121
+ ### 5. Intelligent Search Cookies
122
+
123
+ VTEX IS requires `vtex_is_session` and `vtex_is_anonymous` cookies (UUIDs).
124
+
125
+ **Pattern in middleware.ts**:
126
+
127
+ ```typescript
128
+ // Generate if missing
129
+ if (!cookieHeader.includes("vtex_is_session")) {
130
+ const sessionId = crypto.randomUUID();
131
+ // Set on response
132
+ }
133
+ ```
134
+
135
+ ### 6. HttpOnly Cookies
136
+
137
+ `VtexIdclientAutCookie` is HttpOnly — **cannot** be read via `document.cookie`.
138
+
139
+ **Wrong**: Client-side hooks checking `document.cookie` for auth status.
140
+ **Correct**: `useUser` calls `/api/sessions?items=profile.email` server-side.
141
+
142
+ ### 7. Hooks Completeness
143
+
144
+ Compare with original `deco-cx/apps` hooks:
145
+
146
+ | Hook | Must Have |
147
+ |------|-----------|
148
+ | `useCart` | `addItems`, `updateQuantity`, `removeItem`, `addCoupons`, `fetchCart` |
149
+ | `useUser` | Server-side session check via `/api/sessions` |
150
+ | `useWishlist` | `add`, `remove`, `toggle`, `isInWishlist` |
151
+
152
+ ### 8. transform.ts Parity
153
+
154
+ All exported functions must match the original:
155
+
156
+ ```
157
+ toProduct, toProductPage, pickSku, aggregateOffers, forceHttpsOnAssets,
158
+ sortProducts, filtersFromURL, mergeFacets, legacyFacetToFilter,
159
+ toFilter, categoryTreeToNavbar, toBrand, toReview, toInventories,
160
+ toPlace, toPostalAddress, parsePageType, normalizeFacet
161
+ ```
162
+
163
+ Critical: `seller: sellerId` (not `sellerName`) in `buildOffer`.
164
+
165
+ ### 9. Page Structure (schema.org)
166
+
167
+ | Page | Required Structure |
168
+ |------|--------------------|
169
+ | PDP | `ProductDetailsPage` with `breadcrumbList` + `product` (via `toProductPage`) + `seo` |
170
+ | PLP | `ProductListingPage` with `BreadcrumbList` + `filters` + `products` + `pageInfo` + `sortOptions` + `seo` |
171
+
172
+ ### 10. No Debug Logs in Production
173
+
174
+ ```bash
175
+ rg "console\.log" vtex/ --glob '*.ts'
176
+ ```
177
+
178
+ Only acceptable: 1x startup log in `client.ts`. All others should be `console.error` or `console.warn` in catch blocks.
179
+
180
+ ## Common Fixes
181
+
182
+ ### Fix: Header uses string instead of constant
183
+
184
+ ```typescript
185
+ // Before
186
+ headers: { VtexidClientAutCookie: authCookie }
187
+ // After
188
+ import { VTEX_AUTH_COOKIE } from "../utils/vtexId";
189
+ headers: { [VTEX_AUTH_COOKIE]: authCookie }
190
+ ```
191
+
192
+ ### Fix: Missing expectedOrderFormSections
193
+
194
+ ```typescript
195
+ // Before
196
+ await vtexFetch<OrderForm>(`/api/checkout/pub/orderForm`, { method: "POST", headers });
197
+ // After
198
+ import { DEFAULT_EXPECTED_SECTIONS } from "../actions/checkout";
199
+ await vtexFetch<OrderForm>(`/api/checkout/pub/orderForm`, {
200
+ method: "POST", headers,
201
+ body: JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS }),
202
+ });
203
+ ```
204
+
205
+ ### Fix: Missing salesChannel in catalog
206
+
207
+ ```typescript
208
+ // Before
209
+ return vtexFetch<T[]>(`/api/catalog_system/pub/products/search/?${params}`);
210
+ // After
211
+ const { salesChannel } = getVtexConfig();
212
+ if (salesChannel) params.set("sc", salesChannel);
213
+ return vtexFetch<T[]>(`/api/catalog_system/pub/products/search/?${params}`);
214
+ ```
215
+
216
+ ## Validation
217
+
218
+ After all fixes, run:
219
+
220
+ ```bash
221
+ # TypeScript
222
+ npx -p typescript tsc --noEmit
223
+
224
+ # No hardcoded cookie strings
225
+ rg "VtexIdclientAutCookie" vtex/ --glob '!vtex/utils/vtexId.ts' --glob '!*.md'
226
+
227
+ # No debug logs
228
+ rg "console\.log" vtex/ --glob '*.ts' --glob '!client.ts'
229
+
230
+ # No trailing whitespace
231
+ rg "\s+$" vtex/ --glob '*.ts'
232
+ ```
233
+
234
+ All must return 0 results (except TypeScript which exits 0).
@@ -0,0 +1,270 @@
1
+ ---
2
+ name: deco-async-rendering-architecture
3
+ description: Architecture and internals of Async Section Rendering in @decocms/start. Documents the server-side eager/deferred split (resolve.ts) using CMS Lazy.tsx wrappers as source of truth, client-side IntersectionObserver loading (DecoPageRenderer.tsx), per-section SWR caching (sectionLoaders.ts), bot detection for SEO, the loadDeferredSection server function, and the full request flow from CMS page resolution to on-scroll hydration. Use when debugging async rendering, extending the framework, understanding how deferred sections are resolved, or troubleshooting why a section is/isn't being deferred.
4
+ ---
5
+
6
+ # Deco Async Section Rendering — Framework Architecture
7
+
8
+ Internal documentation for the async section rendering system in `@decocms/start`.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Debugging why a section is or isn't being deferred
13
+ - Understanding the full request flow from CMS resolution to on-scroll loading
14
+ - Extending the async rendering system (new cache tiers, new deferral strategies)
15
+ - Fixing issues with deferred section data resolution
16
+ - Understanding how bot detection and SEO safety work
17
+ - Working on `@decocms/start` framework code
18
+
19
+ ---
20
+
21
+ ## Problem Solved
22
+
23
+ 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.
24
+
25
+ ## Architecture Overview
26
+
27
+ ```
28
+ Request → resolveDecoPage()
29
+ ├─ resolveSectionsList() → unwrap flags/blocks to get raw section array
30
+ ├─ shouldDeferSection() → classify each section as eager or deferred
31
+ │ ├─ Eager: resolveRawSection() → full CMS + commerce resolution
32
+ │ └─ Deferred: resolveSectionShallow() → component key + raw CMS props only
33
+ ├─ runSectionLoaders() → enrich eager sections (server loaders)
34
+ └─ Return { resolvedSections, deferredSections }
35
+
36
+ Client render → DecoPageRenderer
37
+ ├─ mergeSections() → interleave eager + deferred by original index
38
+ ├─ Eager: <Suspense><LazyComponent .../></Suspense>
39
+ └─ Deferred: <DeferredSectionWrapper>
40
+ ├─ preloadSectionModule() → get LoadingFallback early
41
+ ├─ Render skeleton (custom LoadingFallback or generic)
42
+ ├─ IntersectionObserver(rootMargin: 300px)
43
+ └─ On intersect: loadDeferredSection serverFn
44
+ ├─ resolveDeferredSection() → resolve __resolveType refs in rawProps
45
+ ├─ runSingleSectionLoader() → enrich with server loader
46
+ └─ Return ResolvedSection → render real component with fade-in
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Deferral Strategy: CMS Lazy.tsx as Source of Truth
52
+
53
+ ### How it works now (respectCmsLazy)
54
+
55
+ The deferral decision is driven by **CMS editor choices**, not a global index threshold:
56
+
57
+ 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
58
+ 2. **`foldThreshold`** (default `Infinity`) — fallback for sections NOT wrapped in Lazy; with default `Infinity`, non-wrapped sections are always eager
59
+ 3. **`alwaysEager`** — section keys that override all deferral (Header, Footer, Theme, etc.)
60
+
61
+ ### Why this approach
62
+
63
+ The previous `foldThreshold` approach deferred sections by index position, ignoring editor intent. This caused:
64
+ - Sections that editors wanted eager getting deferred
65
+ - No control per-page (threshold was global)
66
+ - Homepage with 12 sections marked Lazy in CMS showing 0 deferred
67
+
68
+ Now editors control deferral by wrapping sections in `Lazy.tsx` in the CMS admin, and the framework respects that.
69
+
70
+ ### `isCmsLazyWrapped(section)` in `resolve.ts`
71
+
72
+ Detects whether a section is wrapped in `website/sections/Rendering/Lazy.tsx`, either:
73
+ - Directly: `section.__resolveType === "website/sections/Rendering/Lazy.tsx"`
74
+ - Via named block: `section.__resolveType` references a block whose `__resolveType` is `"website/sections/Rendering/Lazy.tsx"`
75
+
76
+ ### `shouldDeferSection(section, flatIndex, cfg, isBotReq)`
77
+
78
+ Updated decision logic:
79
+
80
+ ```
81
+ 1. Bot request? → EAGER (SEO safety)
82
+ 2. No __resolveType? → EAGER (can't classify)
83
+ 3. Is multivariate flag? → EAGER (requires runtime evaluation)
84
+ 4. resolveFinalSectionKey() → walk block refs + Lazy wrappers to find final component
85
+ 5. In alwaysEager set? → EAGER
86
+ 6. isLayoutSection()? → EAGER
87
+ 7. respectCmsLazy && isCmsLazyWrapped(section)? → DEFER
88
+ 8. flatIndex >= foldThreshold? → DEFER (fallback, only if not wrapped)
89
+ 9. Otherwise → EAGER
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Files and Their Roles
95
+
96
+ | File | Layer | Role |
97
+ |------|-------|------|
98
+ | `src/cms/resolve.ts` | Server | Types, config, eager/deferred split, CMS Lazy detection, shallow resolution, full deferred resolution |
99
+ | `src/cms/sectionLoaders.ts` | Server | Section loader registry, layout cache, SWR cacheable sections, `runSingleSectionLoader` |
100
+ | `src/cms/registry.ts` | Shared | Section component registry, `preloadSectionModule` for early LoadingFallback |
101
+ | `src/routes/cmsRoute.ts` | Server | `loadCmsPage`, `loadCmsHomePage`, `loadDeferredSection` server functions |
102
+ | `src/hooks/DecoPageRenderer.tsx` | Client | Merge, render eager/deferred, `DeferredSectionWrapper`, dev warnings |
103
+ | `src/cms/index.ts` | Barrel | Re-exports all public types and functions |
104
+ | `src/routes/index.ts` | Barrel | Re-exports route helpers including `loadDeferredSection` |
105
+
106
+ ---
107
+
108
+ ## Server-Side: Eager/Deferred Split
109
+
110
+ ### Entry point: `resolveDecoPage()` in `resolve.ts`
111
+
112
+ ```
113
+ resolveDecoPage(targetPath, matcherCtx)
114
+ 1. findPageByPath(targetPath) → { page, params }
115
+ 2. Get raw sections array:
116
+ - If page.sections is Array → use directly
117
+ - If page.sections is wrapped (multivariate flag, block ref) → resolveSectionsList()
118
+ 3. For each raw section:
119
+ - If shouldDeferSection() → resolveSectionShallow() → DeferredSection
120
+ - Else → resolveRawSection() (full resolution) → ResolvedSection[]
121
+ 4. Return { resolvedSections, deferredSections }
122
+ ```
123
+
124
+ ### `resolveSectionsList(value, rctx, depth)`
125
+
126
+ 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.
127
+
128
+ ### `resolveFinalSectionKey(section)`
129
+
130
+ Walks block reference chain and unwraps `Lazy` wrappers to find the final registered section component key:
131
+
132
+ ```
133
+ "Header - 01" (named block)
134
+ → { __resolveType: "website/sections/Rendering/Lazy.tsx", section: {...} }
135
+ → { __resolveType: "site/sections/Header/Header.tsx", ...props }
136
+ ```
137
+
138
+ Returns `"site/sections/Header/Header.tsx"`, checked against `alwaysEager` and `isLayoutSection`.
139
+
140
+ ### `resolveSectionShallow(section)`
141
+
142
+ Synchronously follows block refs and unwraps Lazy to extract `component` (final key) and `rawProps` (CMS props as-is). No API calls, no async.
143
+
144
+ ### `resolveDeferredSection(component, rawProps, pagePath, matcherCtx)`
145
+
146
+ Called when client requests a deferred section. Runs full resolution:
147
+ 1. `resolveProps(rawProps, rctx)` — resolves all nested `__resolveType` references
148
+ 2. `normalizeNestedSections(resolvedProps)` — converts nested sections to `{ Component, props }`
149
+ 3. Returns `ResolvedSection` ready for `runSingleSectionLoader`
150
+
151
+ ---
152
+
153
+ ## Server-Side: Section Caching
154
+
155
+ ### Three cache tiers in `sectionLoaders.ts`
156
+
157
+ **Tier 1: Layout sections** (Header, Footer, Theme)
158
+ - 5-minute TTL, in-flight dedup, registered via `registerLayoutSections`
159
+
160
+ **Tier 2: Cacheable sections** (ProductShelf, FAQ)
161
+ - Configurable TTL via `registerCacheableSections`, SWR semantics, LRU eviction at 200 entries
162
+ - Cache key: `component::djb2Hash(JSON.stringify(props))`
163
+
164
+ **Tier 3: Regular sections** — No caching, always fresh.
165
+
166
+ ---
167
+
168
+ ## Client-Side: DeferredSectionWrapper
169
+
170
+ ### Lifecycle
171
+
172
+ ```
173
+ 1. Mount (stableKey = pagePath + component + index)
174
+ ├─ preloadSectionModule(component) → extract LoadingFallback
175
+ └─ Render skeleton (custom or generic DefaultSectionFallback)
176
+
177
+ 2. IntersectionObserver (rootMargin: "300px")
178
+ └─ On intersect (once):
179
+ ├─ loadDeferredSection serverFn
180
+ ├─ On success: render <LazyComponent .../> with fade-in
181
+ └─ On error: render ErrorFallback or null
182
+
183
+ 3. SPA navigation: stableKey changes → reset state (triggered, section, error)
184
+ ```
185
+
186
+ ### Key: stableKey for SPA navigation
187
+
188
+ `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.
189
+
190
+ ---
191
+
192
+ ## Bot Detection (SEO Safety)
193
+
194
+ `isBot(userAgent)` regex detects search engine crawlers. When detected, ALL sections are resolved eagerly — `deferredSections` is empty.
195
+
196
+ ---
197
+
198
+ ## Types
199
+
200
+ ### `AsyncRenderingConfig`
201
+
202
+ ```ts
203
+ interface AsyncRenderingConfig {
204
+ respectCmsLazy: boolean; // Default true — use Lazy.tsx wrappers as deferral source
205
+ foldThreshold: number; // Default Infinity — fallback for non-wrapped sections
206
+ alwaysEager: Set<string>; // Section keys that must always be eager
207
+ }
208
+ ```
209
+
210
+ ### `DeferredSection`
211
+
212
+ ```ts
213
+ interface DeferredSection {
214
+ component: string;
215
+ key: string;
216
+ index: number;
217
+ rawProps: Record<string, unknown>;
218
+ }
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Edge Cases and Gotchas
224
+
225
+ ### 1. CMS Lazy.tsx is the source of truth
226
+ 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`).
227
+
228
+ ### 2. Block references to Lazy
229
+ 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.
230
+
231
+ ### 3. alwaysEager overrides Lazy wrapping
232
+ 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.
233
+
234
+ ### 4. Multivariate flags are always eager
235
+ Individual sections wrapped in `website/flags/multivariate.ts` require runtime matcher evaluation and can't be safely deferred.
236
+
237
+ ### 5. InvalidCharacterError with section rendering
238
+ 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.
239
+
240
+ ### 6. Navigation flash prevention
241
+ 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.
242
+
243
+ ---
244
+
245
+ ## Public API Summary
246
+
247
+ ### From `@decocms/start/cms`
248
+
249
+ | Export | Type | Description |
250
+ |--------|------|-------------|
251
+ | `setAsyncRenderingConfig` | Function | Enable/configure async rendering |
252
+ | `getAsyncRenderingConfig` | Function | Read current config |
253
+ | `registerCacheableSections` | Function | Register sections for SWR loader caching |
254
+ | `runSingleSectionLoader` | Function | Run a single section's loader |
255
+ | `resolveDeferredSection` | Function | Fully resolve a deferred section's raw props |
256
+ | `preloadSectionModule` | Function | Eagerly import a section to extract LoadingFallback |
257
+
258
+ ### From `@decocms/start/routes`
259
+
260
+ | Export | Type | Description |
261
+ |--------|------|-------------|
262
+ | `loadDeferredSection` | ServerFn | Server function to resolve + enrich deferred section on demand |
263
+
264
+ ### From `@decocms/start/hooks`
265
+
266
+ | Export | Type | Description |
267
+ |--------|------|-------------|
268
+ | `DecoPageRenderer` | Component | Renders page with eager + deferred section support |
269
+ | `SectionRenderer` | Component | Renders a single section by registry key |
270
+ | `SectionList` | Component | Renders an array of sections |