@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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- 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
|