@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,443 @@
1
+ ---
2
+ name: deco-api-call-dedup
3
+ description: Detect and fix duplicate/N+1 API calls in Deco TanStack storefronts. Covers vtexCachedFetch SWR cache for all VTEX GET calls, slugCache via fetchWithCache, cross-selling SWR cache, usePriceSimulationBatch for batching simulation POSTs, PLP path filtering to avoid spurious pagetype calls, pageType dedup, site loader registration, cachedLoader inflight dedup in dev mode, and HAR analysis techniques. Use when server logs show repeated VTEX API calls, PDP/PLP loads trigger excessive calls, simulation calls happen one-by-one, or "Unhandled resolver" warnings appear.
4
+ ---
5
+
6
+ # API Call Deduplication & Batching
7
+
8
+ Patterns for eliminating redundant VTEX API calls in Deco storefronts on TanStack Start. These patterns reduced PDP API calls from 40+ to ~8 and PLP spurious calls from 15+ to near-zero on `espacosmart-storefront`. All VTEX GET calls now go through `vtexCachedFetch` with SWR (3 min TTL) and in-flight deduplication.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Server logs show duplicate `search/{slug}/p` calls for the same product
13
+ - Cross-selling endpoints (`similars`, `suggestions`, `showtogether`) called multiple times with the same ID
14
+ - `simulation` POST called once per product instead of batched
15
+ - PDP page load triggers 20+ VTEX API calls
16
+ - HAR analysis shows waterfall of sequential API calls
17
+
18
+ ---
19
+
20
+ ## Pattern 1: Slug Search Deduplication (`slugCache`) via `vtexCachedFetch`
21
+
22
+ ### Problem
23
+
24
+ Multiple section loaders call `search/{slug}/p` for the same product:
25
+ - `productDetailsPage.ts` (main PDP loader)
26
+ - `relatedProducts.ts` (needs `productId` from slug)
27
+ - Any section that resolves a product by slug
28
+
29
+ ### Solution (Current)
30
+
31
+ `slugCache.ts` now delegates to `vtexCachedFetch`, which provides both in-flight deduplication AND SWR caching (3 min TTL for 200 responses). No manual inflight Map needed:
32
+
33
+ ```typescript
34
+ // vtex/utils/slugCache.ts
35
+ import { vtexCachedFetch, getVtexConfig } from "../client";
36
+ import type { LegacyProduct } from "./types";
37
+
38
+ export function searchBySlug(linkText: string): Promise<LegacyProduct[] | null> {
39
+ const config = getVtexConfig();
40
+ const sc = config.salesChannel;
41
+ const scParam = sc ? `?sc=${sc}` : "";
42
+
43
+ return vtexCachedFetch<LegacyProduct[]>(
44
+ `/api/catalog_system/pub/products/search/${linkText}/p${scParam}`,
45
+ ).catch((err) => {
46
+ console.error(`[VTEX] searchBySlug error for "${linkText}":`, err);
47
+ return null;
48
+ });
49
+ }
50
+
51
+ export async function resolveProductIdBySlug(slug: string): Promise<string | null> {
52
+ const products = await searchBySlug(slug);
53
+ return products?.length ? products[0].productId : null;
54
+ }
55
+ ```
56
+
57
+ ### Key Change from Previous Version
58
+
59
+ Before: manual `inflight` Map with `setTimeout(() => inflight.delete(...), 5_000)`
60
+ After: `vtexCachedFetch` handles dedup + SWR automatically via `fetchWithCache` (see `deco-vtex-fetch-cache` skill)
61
+
62
+ ### Usage
63
+
64
+ ```typescript
65
+ // In productDetailsPage.ts
66
+ import { searchBySlug } from "../utils/slugCache";
67
+ const products = await searchBySlug(linkText);
68
+
69
+ // In relatedProducts.ts
70
+ import { resolveProductIdBySlug } from "../utils/slugCache";
71
+ const productId = await resolveProductIdBySlug(slug);
72
+ ```
73
+
74
+ ### Impact
75
+
76
+ Before: 3-4 calls to `search/{slug}/p` per PDP load
77
+ After: 1 call, cached for 3 min across all loaders and subsequent page loads
78
+
79
+ ---
80
+
81
+ ## Pattern 2: Cross-Selling via `vtexCachedFetch`
82
+
83
+ ### Problem
84
+
85
+ Multiple loaders request cross-selling data for the same product:
86
+
87
+ ```
88
+ GET /crossselling/similars/58
89
+ GET /crossselling/suggestions/58
90
+ GET /crossselling/whoboughtalsobought/58
91
+ GET /crossselling/showtogether/58
92
+ ```
93
+
94
+ When `relatedProducts.ts` runs multiple times (e.g., for "similars" shelf AND "suggestions" shelf), the same productId+type gets fetched twice.
95
+
96
+ ### Solution (Current)
97
+
98
+ `relatedProducts.ts` now uses `vtexCachedFetch` instead of a manual `crossSellingInflight` Map. The SWR cache handles both dedup and 3-min TTL:
99
+
100
+ ```typescript
101
+ import { vtexCachedFetch, getVtexConfig } from "../client";
102
+
103
+ function fetchCrossSelling(
104
+ type: CrossSellingType,
105
+ productId: string,
106
+ ): Promise<LegacyProduct[]> {
107
+ return vtexCachedFetch<LegacyProduct[]>(
108
+ `/api/catalog_system/pub/products/crossselling/${type}/${productId}`,
109
+ ).catch((err) => {
110
+ console.error(`[VTEX] crossselling/${type}/${productId} error:`, err);
111
+ return [] as LegacyProduct[];
112
+ });
113
+ }
114
+ ```
115
+
116
+ ### Key Change from Previous Version
117
+
118
+ Before: manual `crossSellingInflight` Map with `setTimeout` cleanup
119
+ After: `vtexCachedFetch` provides dedup + SWR. Subsequent calls within 3 min return cached data instantly.
120
+
121
+ ### Always `.catch(() => [])` on Cross-Selling
122
+
123
+ VTEX returns 404 for products without cross-selling data. An unhandled 404 crashes the entire section loader:
124
+
125
+ ```typescript
126
+ // BAD — 404 kills the PDP
127
+ const related = await vtexFetch(`/crossselling/showtogether/${id}`);
128
+
129
+ // GOOD — graceful fallback
130
+ const related = await fetchCrossSelling("showtogether", id);
131
+ // vtexCachedFetch throws for non-ok responses, .catch returns []
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Pattern 3: Price Simulation Batching
137
+
138
+ ### Problem
139
+
140
+ Product shelves call `simulation` POST once per product (N+1):
141
+
142
+ ```
143
+ POST /orderForms/simulation (item: sku-1)
144
+ POST /orderForms/simulation (item: sku-2)
145
+ POST /orderForms/simulation (item: sku-3)
146
+ ...
147
+ ```
148
+
149
+ ### Solution
150
+
151
+ Create a batch simulation function that sends all SKUs in one call:
152
+
153
+ ```typescript
154
+ // hooks/usePriceSimulationBatch.ts
155
+ import { simulateCart } from "@decocms/apps/vtex/actions/checkout";
156
+
157
+ interface SimulationResult {
158
+ priceSimulation: number;
159
+ noInterestInstallmentValue: string | null;
160
+ installmentsObject: { value: number; numberOfInstallments: number } | null;
161
+ }
162
+
163
+ export async function usePriceSimulationBatch(
164
+ skuIds: (string | undefined)[],
165
+ request: Request,
166
+ ): Promise<SimulationResult[]> {
167
+ const validIds = skuIds.filter(Boolean) as string[];
168
+ if (!validIds.length) return skuIds.map(() => defaultResult());
169
+
170
+ const items = validIds.map((id) => ({
171
+ id: Number(id),
172
+ quantity: 1,
173
+ seller: "1",
174
+ }));
175
+
176
+ const cookieHeader = request.headers.get("cookie") ?? undefined;
177
+ const simulation = await simulateCart(items, "", "BRA", 0, cookieHeader);
178
+
179
+ const resultMap = new Map<string, SimulationResult>();
180
+ for (const item of simulation.items ?? []) {
181
+ resultMap.set(String(item.id), extractPriceData(item));
182
+ }
183
+
184
+ return skuIds.map((id) => resultMap.get(id ?? "") ?? defaultResult());
185
+ }
186
+ ```
187
+
188
+ ### Usage
189
+
190
+ ```typescript
191
+ // In section loaders — batch all IDs
192
+ const allIds = [mainProductId, ...relatedProductIds];
193
+ const allSimulations = await usePriceSimulationBatch(allIds, request);
194
+ const mainSim = allSimulations[0];
195
+ const relatedSims = allSimulations.slice(1);
196
+ ```
197
+
198
+ ### Impact
199
+
200
+ Before: N `simulation` POST calls (one per product in shelf)
201
+ After: 1 `simulation` POST call with all items batched
202
+
203
+ ---
204
+
205
+ ## Pattern 4: `cachedLoader` In-Flight Dedup in Dev Mode
206
+
207
+ ### Problem
208
+
209
+ `createCachedLoader` completely disables caching in dev mode. This means even concurrent calls for the same key hit the API independently.
210
+
211
+ ### Solution
212
+
213
+ Keep SWR cache disabled in dev, but enable in-flight deduplication:
214
+
215
+ ```typescript
216
+ // In cachedLoader.ts
217
+ export function createCachedLoader<T>(name: string, loaderFn: LoaderFn<T>, opts: CacheOptions) {
218
+ const inflight = new Map<string, Promise<T>>();
219
+
220
+ return async (props: any): Promise<T> => {
221
+ const key = `${name}:${JSON.stringify(props)}`;
222
+
223
+ if (isDev) {
224
+ // Dev: skip SWR cache but deduplicate concurrent calls
225
+ const existing = inflight.get(key);
226
+ if (existing) return existing;
227
+
228
+ const promise = loaderFn(props).finally(() => inflight.delete(key));
229
+ inflight.set(key, promise);
230
+ return promise;
231
+ }
232
+
233
+ // Production: full SWR cache
234
+ return swr(key, () => loaderFn(props), opts);
235
+ };
236
+ }
237
+ ```
238
+
239
+ ### Why In-Flight Dedup Matters in Dev
240
+
241
+ During SSR, multiple sections resolve concurrently. Without dedup, the PDP loader runs 2-3 times for the same slug:
242
+ 1. ProductMain section → `cachedPDP({ slug })`
243
+ 2. Related Products section → `cachedPDP({ slug })` (to get productId)
244
+ 3. Breadcrumb → `cachedPDP({ slug })`
245
+
246
+ With inflight dedup, only 1 actual API call, other callers await the same Promise.
247
+
248
+ ---
249
+
250
+ ## Pattern 5: PLP Path Filtering — Avoid Spurious `pageType` Calls
251
+
252
+ ### Problem
253
+
254
+ The PLP loader's `pageTypesFromPath(__pagePath)` receives invalid paths like `/image/checked.png`, `/.well-known/appspecific/...`, `/assets/sprite.svg`. Each path segment triggers a VTEX `pagetype` API call, wasting 5+ calls on non-page URLs.
255
+
256
+ ### Solution
257
+
258
+ Filter invalid paths before calling `pageTypesFromPath`:
259
+
260
+ ```typescript
261
+ // In productListingPage.ts
262
+ const INVALID_PLP_PREFIXES = [
263
+ "/image/", "/.well-known/", "/assets/", "/favicon",
264
+ "/_serverFn/", "/_build/", "/node_modules/",
265
+ ];
266
+
267
+ function isValidPLPPath(path: string): boolean {
268
+ const lower = path.toLowerCase();
269
+ if (INVALID_PLP_PREFIXES.some((p) => lower.startsWith(p))) return false;
270
+ const ext = lower.split("/").pop()?.split(".")?.pop();
271
+ if (ext && ["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "css", "js", "woff", "woff2", "ttf"].includes(ext)) {
272
+ return false;
273
+ }
274
+ return true;
275
+ }
276
+
277
+ // Usage:
278
+ if (facets.length === 0 && __pagePath && __pagePath !== "/" && __pagePath !== "/*" && isValidPLPPath(__pagePath)) {
279
+ const allPageTypes = await pageTypesFromPath(__pagePath);
280
+ // ...
281
+ }
282
+ ```
283
+
284
+ ### Impact
285
+
286
+ Eliminates 5+ spurious VTEX API calls on PLP pages that have asset URLs in the path resolution pipeline.
287
+
288
+ ---
289
+
290
+ ## Pattern 6: `pageTypesFromPath` Dedup via `vtexCachedFetch`
291
+
292
+ ### Problem
293
+
294
+ `pageTypesFromPath` calls VTEX's `pagetype` API for each path segment (cumulative). When multiple PLP sections resolve the same path, each segment gets fetched multiple times.
295
+
296
+ ### Solution
297
+
298
+ Each individual `pagetype` call now goes through `vtexCachedFetch` with SWR:
299
+
300
+ ```typescript
301
+ function cachedPageType(term: string): Promise<PageType> {
302
+ return vtexCachedFetch<PageType>(`/api/catalog_system/pub/portal/pagetype/${term}`);
303
+ }
304
+
305
+ export async function pageTypesFromPath(pagePath: string): Promise<PageType[]> {
306
+ const segments = pagePath.split("/").filter(Boolean);
307
+ return Promise.all(
308
+ segments.map((_, index) => {
309
+ const term = segments.slice(0, index + 1).join("/");
310
+ return cachedPageType(term);
311
+ }),
312
+ );
313
+ }
314
+ ```
315
+
316
+ ### Impact
317
+
318
+ Page type results are cached for 3 min. Concurrent and subsequent calls for the same segment share the same cached response.
319
+
320
+ ---
321
+
322
+ ## Pattern 7: Register All Site Loaders
323
+
324
+ ### Problem
325
+
326
+ Custom site loaders like `site/loaders/Layouts/ProductCard.tsx` and `site/loaders/Search/colors.ts` appear in CMS blocks but aren't registered in `setup.ts`. This causes `[CMS] Unhandled resolver: site/loaders/...` warnings and missing data.
327
+
328
+ ### Solution
329
+
330
+ Register passthrough loaders in `COMMERCE_LOADERS` in `setup.ts`:
331
+
332
+ ```typescript
333
+ const COMMERCE_LOADERS: Record<string, (props: any) => Promise<any>> = {
334
+ // ... existing commerce loaders ...
335
+ "site/loaders/Layouts/ProductCard.tsx": async (props: any) => props.layout ?? props,
336
+ "site/loaders/Search/colors.ts": async (props: any) => ({ colors: props.colors ?? [] }),
337
+ };
338
+ ```
339
+
340
+ ### How to Find Missing Loaders
341
+
342
+ Search server logs for "Unhandled resolver":
343
+ ```bash
344
+ rg "Unhandled resolver" # in terminal output
345
+ ```
346
+
347
+ Then check if the referenced loader exists in `src/loaders/` and add a corresponding entry in `setup.ts`.
348
+
349
+ ---
350
+
351
+ ## Diagnosing API Call Issues
352
+
353
+ ### Server Logs
354
+
355
+ Add prefixed logging to VTEX fetch:
356
+
357
+ ```typescript
358
+ console.log(`[vtex] GET ${url}`);
359
+ const result = await fetch(url);
360
+ console.log(`[vtex] ${result.status} GET ${url} ${Date.now() - start}ms`);
361
+ ```
362
+
363
+ ### HAR Analysis
364
+
365
+ ```python
366
+ import json
367
+ with open('localhost.har') as f:
368
+ har = json.load(f)
369
+
370
+ # Count VTEX API calls by endpoint
371
+ from collections import Counter
372
+ vtex_calls = Counter()
373
+ for e in har['log']['entries']:
374
+ url = e['request']['url']
375
+ if 'vtexcommercestable' not in url:
376
+ continue
377
+ # Extract endpoint pattern
378
+ path = url.split('.com.br')[1].split('?')[0] if '.com.br' in url else url
379
+ vtex_calls[path] += 1
380
+
381
+ for path, count in vtex_calls.most_common(20):
382
+ print(f" {count}x {path}")
383
+ ```
384
+
385
+ ### Common N+1 Patterns to Watch For
386
+
387
+ | Pattern | Symptom | Fix |
388
+ |---------|---------|-----|
389
+ | `search/{slug}/p` called N times | Multiple section loaders resolve same product | `vtexCachedFetch` via `slugCache` |
390
+ | `crossselling/{type}/{id}` duplicated | Same product ID across multiple related-products sections | `vtexCachedFetch` in `relatedProducts.ts` |
391
+ | `simulation` called per product | Product shelves simulate one-by-one | `usePriceSimulationBatch` |
392
+ | `intelligent-search` for Header shelves | Header re-resolved on every navigation | Layout caching + `fetchWithCache` for IS |
393
+ | `orderForm` called multiple times | Multiple components check cart state | `useCart` singleton |
394
+ | `pagetype` for asset URLs | PLP loader resolving `/image/...` paths | `isValidPLPPath` filter |
395
+ | `pagetype` called N times for same segment | Multiple PLP sections resolve same path | `vtexCachedFetch` in `cachedPageType` |
396
+ | `Unhandled resolver: site/loaders/...` | Custom site loaders not registered | Register in `setup.ts` COMMERCE_LOADERS |
397
+
398
+ ---
399
+
400
+ ## Common Errors
401
+
402
+ ### `ERR_MODULE_NOT_FOUND` for slugCache
403
+
404
+ **Note**: This error has been resolved. Imports within `@decocms/apps` now use extensionless paths (standard for Node/Vite). If you see this error, ensure the import doesn't have `.ts` extension:
405
+
406
+ ```typescript
407
+ // GOOD (current)
408
+ import { searchBySlug } from "../utils/slugCache";
409
+ import { vtexCachedFetch } from "../client";
410
+ import { fetchWithCache } from "./utils/fetchCache";
411
+ ```
412
+
413
+ ### `crossselling//showtogether` (empty productId)
414
+
415
+ The productId was `undefined`. Always guard:
416
+
417
+ ```typescript
418
+ if (!mainProduct) return { ...props };
419
+ const productGroupId = mainProduct.inProductGroupWithID ?? mainProduct.productID ?? "";
420
+ if (!productGroupId) return { ...props };
421
+ ```
422
+
423
+ ### `config is not defined` in productDetailsPage
424
+
425
+ If `getVtexConfig()` is removed during refactoring, the `salesChannel` query param is lost:
426
+
427
+ ```typescript
428
+ const config = getVtexConfig();
429
+ const sc = config.salesChannel;
430
+ // Use sc in API URLs: `?sc=${sc}`
431
+ ```
432
+
433
+ ---
434
+
435
+ ## Related Skills
436
+
437
+ | Skill | Purpose |
438
+ |-------|---------|
439
+ | `deco-vtex-fetch-cache` | SWR fetch cache for VTEX APIs (`fetchWithCache`, `vtexCachedFetch`) |
440
+ | `deco-variant-selection-perf` | Eliminate server calls for variant selection |
441
+ | `deco-cms-layout-caching` | Cache layout sections to prevent Header API calls |
442
+ | `deco-loader-n-plus-1-detector` | Automated N+1 detection in Deco loaders |
443
+ | `deco-tanstack-storefront-patterns` | General runtime patterns + loader `cache`/`cacheKey` exports |
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: deco-apps-architecture
3
+ description: Architecture reference for deco-cx/apps monorepo — the canonical Deco commerce integration library. Covers the monorepo structure with 90+ apps (VTEX, Shopify, Nuvemshop, Wake, etc.), shared utils (HTTP client, GraphQL, fetch, cookies, LRU cache), commerce types (schema.org), the app pattern (mod.ts, manifest.gen.ts, actions/, loaders/, hooks/, utils/), and the relationship between deco-cx/apps (Fresh/Deno) and @decocms/apps-start (TanStack/Node). Use when exploring the apps repo, understanding how a Deco app is structured, creating new integrations, or porting apps to TanStack Start.
4
+ globs:
5
+ - "**/deco.ts"
6
+ - "**/manifest.gen.ts"
7
+ - "**/mod.ts"
8
+ - "**/runtime.ts"
9
+ ---
10
+
11
+ ## Sub-documents
12
+
13
+ | Document | Topic |
14
+ |----------|-------|
15
+ | [commerce-types.md](./commerce-types.md) | Schema.org types reference — Product, Offer, PDP, PLP, Analytics |
16
+ | [shared-utils.md](./shared-utils.md) | Shared utilities — HTTP client, GraphQL, fetch, cookie, normalize |
17
+ | [app-pattern.md](./app-pattern.md) | The Deco App Pattern — mod.ts, manifest, runtime, hooks, context |
18
+ | [vtex-deep-structure.md](./vtex-deep-structure.md) | VTEX app deep dive — all 141 files, endpoints, data flow |
19
+ | [website-app.md](./website-app.md) | Website app — routing, SEO, handlers, matchers, A/B testing |
20
+ | [scripts-codegen.md](./scripts-codegen.md) | Scripts & codegen — OpenAPI, GraphQL, templates, CI/CD |
21
+ | [new-app-guide.md](./new-app-guide.md) | Creating a new app — step-by-step guide with commerce template |
22
+
23
+ # deco-cx/apps Architecture
24
+
25
+ Reference for the `deco-cx/apps` monorepo — the canonical library of Deco integrations.
26
+
27
+ ## Monorepo Overview
28
+
29
+ ```
30
+ apps/
31
+ ├── deco.ts # App registry — lists all ~90 apps
32
+ ├── deno.json # Deno config, import map, tasks
33
+ ├── scripts/ # Codegen and project scaffolding
34
+ │ ├── start.ts # OpenAPI/GraphQL codegen + bundle
35
+ │ └── new.ts # App/MCP template generator
36
+ ├── utils/ # Shared utilities (all apps can import)
37
+ ├── commerce/ # Schema.org commerce types + shared loaders
38
+ ├── website/ # Base website app (routing, SEO, analytics, image)
39
+ ├── admin/ # Admin widget types
40
+ ├── compat/ # Legacy compatibility ($live, std)
41
+ ├── workflows/ # Deco workflow engine
42
+ ├── vtex/ # VTEX integration (141 files)
43
+ ├── shopify/ # Shopify integration
44
+ ├── nuvemshop/ # Nuvemshop integration
45
+ ├── wake/ # Wake integration
46
+ ├── wap/ # Wap integration
47
+ └── (80+ more apps) # AI, analytics, CRM, payments, etc.
48
+ ```
49
+
50
+ ## The App Pattern
51
+
52
+ Every app follows the same structure:
53
+
54
+ ```
55
+ {app-name}/
56
+ ├── mod.ts # App factory — exports Props, AppContext, state
57
+ ├── manifest.gen.ts # Auto-generated — registers all blocks
58
+ ├── runtime.ts # Client-side invoke proxy (optional)
59
+ ├── middleware.ts # Request middleware (optional)
60
+ ├── actions/ # Write operations (mutations)
61
+ ├── loaders/ # Read operations (data fetching)
62
+ ├── hooks/ # Client-side Preact hooks (optional)
63
+ ├── sections/ # CMS-renderable UI sections (optional)
64
+ ├── handlers/ # HTTP request handlers (optional)
65
+ ├── components/ # Shared Preact components (optional)
66
+ ├── utils/ # Internal utilities and types
67
+ │ ├── types.ts # API response/request types
68
+ │ ├── client.ts # Typed HTTP client definitions
69
+ │ ├── transform.ts # API → schema.org mapping (commerce apps)
70
+ │ └── openapi/ # Auto-generated OpenAPI types (optional)
71
+ ├── workflows/ # Background workflow definitions (optional)
72
+ └── preview/ # Admin preview UI (optional)
73
+ ```
74
+
75
+ ### mod.ts Pattern
76
+
77
+ ```typescript
78
+ import manifest, { Manifest } from "./manifest.gen.ts";
79
+ import { type App, type AppContext as AC } from "@deco/deco";
80
+
81
+ export type AppContext = AC<ReturnType<typeof MyApp>>;
82
+
83
+ export interface Props {
84
+ account: string;
85
+ apiKey?: Secret;
86
+ // ...
87
+ }
88
+
89
+ export default function MyApp(props: Props) {
90
+ const state = { /* clients, config */ };
91
+ const app: App<Manifest, typeof state> = { manifest, state };
92
+ return app;
93
+ }
94
+ ```
95
+
96
+ ### manifest.gen.ts
97
+
98
+ Auto-generated by Deco. Registers all actions, loaders, handlers, sections, workflows as blocks.
99
+
100
+ ### runtime.ts
101
+
102
+ ```typescript
103
+ import { Manifest } from "./manifest.gen.ts";
104
+ import { proxy } from "@deco/deco/web";
105
+ export const invoke = proxy<Manifest>();
106
+ ```
107
+
108
+ ## Shared Utils (`/utils/`)
109
+
110
+ | File | Purpose |
111
+ |------|---------|
112
+ | `http.ts` | `createHttpClient<T>` — typed HTTP client from OpenAPI specs |
113
+ | `graphql.ts` | `createGraphqlClient` — typed GraphQL client |
114
+ | `fetch.ts` | `fetchSafe`, `fetchAPI`, retry with exponential backoff, `STALE` cache headers |
115
+ | `cookie.ts` | `proxySetCookie`, `getFlagsFromCookies` |
116
+ | `normalize.ts` | `removeDirtyCookies`, `removeNonLatin1Chars` |
117
+ | `lru.ts` | LRU cache implementation |
118
+ | `shortHash.ts` | SHA-256 string hashing |
119
+ | `pool.ts` | Resource pool with acquire/release |
120
+ | `worker.ts` | Web Worker abstraction (Comlink-style) |
121
+ | `dataURI.ts` | Script-to-data-URI conversion |
122
+ | `capitalize.ts` | String capitalization |
123
+
124
+ ## Commerce Module (`/commerce/`)
125
+
126
+ The shared commerce layer — platform-agnostic types and utilities.
127
+
128
+ ```
129
+ commerce/
130
+ ├── types.ts # Schema.org types (Product, Offer, BreadcrumbList, etc.)
131
+ ├── mod.ts # Composes website + platform (vtex/shopify/wake/vnda)
132
+ ├── manifest.gen.ts
133
+ ├── loaders/
134
+ │ ├── extensions/ # Product page/list/PLP enrichment
135
+ │ ├── navbar.ts # Navigation loader
136
+ │ └── product/ # Product query orchestration
137
+ ├── sections/Seo/ # SEO sections for PDP/PLP
138
+ └── utils/
139
+ ├── filters.ts # parseRange, formatRange
140
+ ├── constants.ts # DEFAULT_IMAGE placeholder
141
+ ├── canonical.ts # Canonical URL from breadcrumb
142
+ ├── productToAnalyticsItem.ts # Product → GA4 AnalyticsItem
143
+ └── stateByZip.ts # Brazilian state from ZIP code
144
+ ```
145
+
146
+ ### Key Types (types.ts)
147
+
148
+ | Type | Purpose |
149
+ |------|---------|
150
+ | `Product` | Schema.org Product with offers, images, variants |
151
+ | `ProductGroup` | Product with hasVariant[] |
152
+ | `Offer` / `AggregateOffer` | Pricing and availability |
153
+ | `ProductDetailsPage` | PDP data (product + breadcrumb + SEO) |
154
+ | `ProductListingPage` | PLP data (products + filters + pagination + SEO) |
155
+ | `BreadcrumbList` | Navigation path |
156
+ | `Filter` / `FilterToggle` / `FilterRange` | Faceted search filters |
157
+ | `Suggestion` | Autocomplete results |
158
+ | `AnalyticsItem` | GA4 event item format |
159
+ | `SiteNavigationElement` | Menu/navbar structure |
160
+
161
+ ## VTEX App Structure (`/vtex/`)
162
+
163
+ The largest integration (141 files). For detailed VTEX-specific docs, see the `deco-apps-vtex-porting` and `deco-apps-vtex-review` skills.
164
+
165
+ ```
166
+ vtex/
167
+ ├── actions/ # 43 files across 11 subdirs
168
+ │ ├── cart/ # 16 actions (addItems, updateItems, updateCoupons, etc.)
169
+ │ ├── authentication/# 8 actions (signIn, logout, recovery, etc.)
170
+ │ ├── address/ # 3 (create, update, delete)
171
+ │ ├── session/ # 3 (create, edit, delete)
172
+ │ ├── wishlist/ # 2 (add, remove)
173
+ │ ├── newsletter/ # 2 (subscribe, updateOptIn)
174
+ │ ├── masterdata/ # 2 (create, update document)
175
+ │ └── (orders, payment, profile, review, analytics)
176
+ ├── loaders/ # 50+ files across 15 subdirs
177
+ │ ├── intelligentSearch/ # 6 (PDP, PLP, productList, suggestions, topSearches, validator)
178
+ │ ├── legacy/ # 7 (PDP, PLP, productList, suggestions, brands, pageType, related)
179
+ │ ├── logistics/ # 5 (salesChannel, pickupPoints, stock)
180
+ │ ├── workflow/ # 2 (product, products)
181
+ │ └── (address, cart, categories, collections, orders, payment, profile, session, etc.)
182
+ ├── hooks/ # 5 (context, useCart, useUser, useWishlist, useAutocomplete)
183
+ ├── utils/ # 31 files
184
+ │ ├── transform.ts # Canonical VTEX → schema.org mapping (THE key file)
185
+ │ ├── types.ts # 1320 lines of VTEX API types
186
+ │ ├── client.ts # SP, VTEXCommerceStable client definitions
187
+ │ ├── openapi/ # 12 files — auto-generated from VTEX OpenAPI specs
188
+ │ └── (cookies, segment, intelligentSearch, legacy, vtexId, etc.)
189
+ └── (middleware, handlers/sitemap, sections/Analytics, workflows, components, preview)
190
+ ```
191
+
192
+ ## Shopify App Structure (`/shopify/`)
193
+
194
+ ```
195
+ shopify/
196
+ ├── actions/cart/ # addItems, updateCoupons, updateItems
197
+ ├── actions/order/ # draftOrderCalculate
198
+ ├── actions/user/ # signIn, signUp
199
+ ├── hooks/ # context, useCart, useUser
200
+ ├── loaders/ # PDP, PLP, ProductList, RelatedProducts, cart, shop, user, proxy
201
+ └── utils/
202
+ ├── admin/ # Admin API queries
203
+ ├── storefront/ # Storefront API (GraphQL schema + generated types)
204
+ └── transform.ts # Shopify → schema.org mapping
205
+ ```
206
+
207
+ ## Website App (`/website/`)
208
+
209
+ Base app that all storefronts use. Handles routing, SEO, analytics, image optimization, and theme.
210
+
211
+ Key areas:
212
+ - `handlers/` — Fresh router, proxy, redirect, sitemap
213
+ - `loaders/` — Pages, fonts, images, redirects, secrets, environment
214
+ - `sections/` — Analytics (GA, GTM, Pixel), Rendering, SEO
215
+ - `matchers/` — Audience targeting (device, cookie, date, location, etc.)
216
+ - `flags/` — A/B testing, multivariate, audience segmentation
217
+
218
+ ## Scripts (`/scripts/`)
219
+
220
+ | Script | Purpose |
221
+ |--------|---------|
222
+ | `start.ts` | Runs on `deno task start` — generates OpenAPI types, GraphQL types, bundles |
223
+ | `new.ts` | Scaffolds new app from template — `deno task new` |
224
+
225
+ ## CI/CD (`.github/workflows/`)
226
+
227
+ | Workflow | Trigger | Purpose |
228
+ |----------|---------|---------|
229
+ | `ci.yaml` | Push/PR | Bundle, fmt check, lint, test, bench |
230
+ | `release.yaml` | Tag push | Publish release |
231
+ | `releaser.yaml` | PR/comment | Version bump, tag creation |
232
+
233
+ ## Relationship: apps → apps-start
234
+
235
+ ```
236
+ deco-cx/apps (Fresh/Deno) @decocms/apps-start (TanStack/Node)
237
+ ──────────────────────── ─────────────────────────────────
238
+ vtex/utils/transform.ts → vtex/utils/transform.ts (ported)
239
+ vtex/utils/types.ts → vtex/utils/types.ts (ported)
240
+ vtex/loaders/** → vtex/loaders/** + vtex/inline-loaders/**
241
+ vtex/actions/** → vtex/actions/** (consolidated)
242
+ vtex/hooks/** → vtex/hooks/** (React + TanStack Query)
243
+ commerce/types.ts → commerce/types/commerce.ts
244
+ commerce/utils/** → commerce/utils/** + commerce/sdk/**
245
+ utils/fetch.ts → Replaced by vtexFetch/vtexFetchWithCookies
246
+ utils/http.ts → Not needed (no OpenAPI codegen in apps-start)
247
+ utils/graphql.ts → vtexIOGraphQL in client.ts
248
+ ```
249
+
250
+ Key differences:
251
+ - apps-start has no `mod.ts` / `manifest.gen.ts` / `runtime.ts` (no Deco framework)
252
+ - apps-start uses `configureVtex()` instead of app factory pattern
253
+ - apps-start loaders are pure async functions, not Deco blocks
254
+ - apps-start hooks use `@tanstack/react-query` instead of `@preact/signals`
255
+ - apps-start has no OpenAPI codegen — uses manual `vtexFetch` calls