@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,341 @@
|
|
|
1
|
+
# Client-Side Patterns
|
|
2
|
+
|
|
3
|
+
How Deco storefronts handle client-side interactivity via islands, the invoke proxy, signals, analytics, and SDK utilities.
|
|
4
|
+
|
|
5
|
+
## Invoke Proxy (`runtime.ts`)
|
|
6
|
+
|
|
7
|
+
Every site creates a typed invoke proxy that bridges client-side code to server loaders/actions:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// runtime.ts
|
|
11
|
+
import { proxy } from "@deco/deco/web";
|
|
12
|
+
import type { Manifest } from "./manifest.gen.ts";
|
|
13
|
+
import type { Manifest as VTEXManifest } from "apps/vtex/manifest.gen.ts";
|
|
14
|
+
|
|
15
|
+
export const invoke = proxy<Manifest & VTEXManifest>();
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Merging `Manifest & VTEXManifest` gives typed access to both site-level and VTEX loaders/actions.
|
|
19
|
+
|
|
20
|
+
### Usage Patterns
|
|
21
|
+
|
|
22
|
+
**Direct typed call:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { invoke } from "../runtime.ts";
|
|
26
|
+
|
|
27
|
+
const products = await invoke.vtex.loaders.legacy.productList({
|
|
28
|
+
fq: `productId:${id}`,
|
|
29
|
+
count: 1,
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Key-based call (dynamic loader from CMS):**
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const suggestions = await invoke({
|
|
37
|
+
key: __resolveType, // e.g., "vtex/loaders/intelligentSearch/suggestions.ts"
|
|
38
|
+
props: { query, count: 5 },
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This pattern is used when the loader key comes from a `Resolved<T>` prop set in the CMS, allowing the admin to choose which loader provides data.
|
|
43
|
+
|
|
44
|
+
**Action call:**
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const user = await invoke.site.actions.checkUser();
|
|
48
|
+
const orders = await invoke.site.loaders.listActiveOrders();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Islands Architecture
|
|
52
|
+
|
|
53
|
+
Islands are Preact components that hydrate on the client. They're the boundary between server-rendered HTML and client-side interactivity.
|
|
54
|
+
|
|
55
|
+
### When to Use Islands
|
|
56
|
+
|
|
57
|
+
| Use Island | Use Section/Component |
|
|
58
|
+
|------------|----------------------|
|
|
59
|
+
| Click handlers, form inputs | Static display |
|
|
60
|
+
| Client-side state (signals) | Server-rendered data |
|
|
61
|
+
| API calls (invoke) | CMS-configured layout |
|
|
62
|
+
| Animations, IntersectionObserver | SEO-critical content |
|
|
63
|
+
|
|
64
|
+
### Island File Convention
|
|
65
|
+
|
|
66
|
+
Islands live in `islands/` and are registered in `fresh.gen.ts`:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// islands/CartRetrofit/AddToCartButton.tsx
|
|
70
|
+
import { useSignal } from "@preact/signals";
|
|
71
|
+
import { useCart } from "apps/vtex/hooks/useCart.ts";
|
|
72
|
+
import { sendAnalyticsEvent } from "$store/sdk/analyticsRetrofit.tsx";
|
|
73
|
+
|
|
74
|
+
interface Props {
|
|
75
|
+
skuId: string;
|
|
76
|
+
sellerId: string;
|
|
77
|
+
productName: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default function AddToCartButton({ skuId, sellerId, productName }: Props) {
|
|
81
|
+
const loading = useSignal(false);
|
|
82
|
+
const { addItems } = useCart();
|
|
83
|
+
|
|
84
|
+
const handleClick = async () => {
|
|
85
|
+
loading.value = true;
|
|
86
|
+
await addItems({ orderItems: [{ id: skuId, seller: sellerId, quantity: 1 }] });
|
|
87
|
+
sendAnalyticsEvent({ name: "add_to_cart", params: { items: [{ item_name: productName }] } });
|
|
88
|
+
loading.value = false;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<button onClick={handleClick} disabled={loading.value}>
|
|
93
|
+
{loading.value ? "Adding..." : "Add to Cart"}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Signals (`@preact/signals`)
|
|
100
|
+
|
|
101
|
+
Deco sites use Preact signals for reactive client-side state. Common patterns:
|
|
102
|
+
|
|
103
|
+
### Global UI State
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// sdk/useUIRetrofit.ts
|
|
107
|
+
import { signal } from "@preact/signals";
|
|
108
|
+
|
|
109
|
+
const displayCart = signal(false);
|
|
110
|
+
const displayMenu = signal(false);
|
|
111
|
+
const displaySearchDrawer = signal(false);
|
|
112
|
+
const displayWishlistModal = signal(false);
|
|
113
|
+
|
|
114
|
+
export const useUI = () => ({
|
|
115
|
+
displayCart,
|
|
116
|
+
displayMenu,
|
|
117
|
+
displaySearchDrawer,
|
|
118
|
+
displayWishlistModal,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Islands import `useUI()` to toggle drawers, modals, and overlays:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const { displayCart } = useUI();
|
|
126
|
+
displayCart.value = true; // opens cart drawer
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Loading / Pagination State
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// sdk/useShowMoreRetrofit.ts
|
|
133
|
+
import { signal } from "@preact/signals";
|
|
134
|
+
|
|
135
|
+
const currentPage = signal(0);
|
|
136
|
+
const loading = signal(false);
|
|
137
|
+
|
|
138
|
+
export const useShowMore = () => ({ currentPage, loading });
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Local Component State
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { useSignal } from "@preact/signals";
|
|
145
|
+
|
|
146
|
+
export default function SizeSelector() {
|
|
147
|
+
const selectedSize = useSignal<string | null>(null);
|
|
148
|
+
const expandText = useSignal(false);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div>
|
|
152
|
+
<button onClick={() => (expandText.value = !expandText.value)}>
|
|
153
|
+
{expandText.value ? "Less" : "More"}
|
|
154
|
+
</button>
|
|
155
|
+
{/* size buttons update selectedSize.value */}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## usePartialSection (Infinite Scroll)
|
|
162
|
+
|
|
163
|
+
`usePartialSection` enables server-rendered partial updates without full page navigation. Used for "Load More" in PLPs:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// components/searchRetrofit/Result.tsx
|
|
167
|
+
import { usePartialSection } from "deco/hooks/usePartialSection.ts";
|
|
168
|
+
|
|
169
|
+
function LoadMoreButton({ nextPage }: { nextPage: string }) {
|
|
170
|
+
return (
|
|
171
|
+
<button
|
|
172
|
+
{...usePartialSection({
|
|
173
|
+
href: nextPage,
|
|
174
|
+
mode: "append", // appends new content below existing
|
|
175
|
+
})}
|
|
176
|
+
>
|
|
177
|
+
Load More
|
|
178
|
+
</button>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
How it works:
|
|
184
|
+
1. Button click triggers a fetch to `/deco/render` with the section's resolve chain
|
|
185
|
+
2. Server renders the section with updated props (next page)
|
|
186
|
+
3. Response HTML is appended to the DOM (in `append` mode)
|
|
187
|
+
4. No client-side JS needed for the content itself
|
|
188
|
+
|
|
189
|
+
## Analytics
|
|
190
|
+
|
|
191
|
+
### Event Dispatch
|
|
192
|
+
|
|
193
|
+
Two systems work together:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// sdk/analyticsRetrofit.tsx
|
|
197
|
+
export function sendEvent(event: AnalyticsEvent) {
|
|
198
|
+
globalThis.window.DECO.events.dispatch(event);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// components/scriptRetrofit/sendAnalyticsEvent.tsx
|
|
204
|
+
export function sendAnalyticsEvent(event: AnalyticsEvent) {
|
|
205
|
+
// 1. Push to GTM dataLayer
|
|
206
|
+
globalThis.dataLayer?.push({ event: event.name, ecommerce: event.params });
|
|
207
|
+
// 2. Dispatch to Deco events system
|
|
208
|
+
globalThis.window.DECO?.events?.dispatch(event);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Event Components
|
|
213
|
+
|
|
214
|
+
**SendEventOnClick** -- fires when element is clicked:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
<SendEventOnClick event={{
|
|
218
|
+
name: "select_item",
|
|
219
|
+
params: { items: [{ item_id: product.sku, item_name: product.name }] }
|
|
220
|
+
}} id={elementId} />
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Injects a script that adds a click listener to the element.
|
|
224
|
+
|
|
225
|
+
**SendEventOnLoad** -- fires when component mounts:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<SendEventOnLoad event={{
|
|
229
|
+
name: "view_item_list",
|
|
230
|
+
params: { items: products.map(productToAnalyticsItem) }
|
|
231
|
+
}} />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**IntersectionObserver-based** -- fires when element is visible:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
<SendAnalyticsEventOnLoad rootId={elementId} event={{
|
|
238
|
+
name: "view_promotion",
|
|
239
|
+
params: { creative_name: bannerName }
|
|
240
|
+
}} />
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Data Attributes
|
|
244
|
+
|
|
245
|
+
Product cards use `data-deco` and `data-product-*` for tracking:
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<a href={url} data-deco="view-product" data-product-id={productId} data-product-name={name}>
|
|
249
|
+
...
|
|
250
|
+
</a>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### VTEX Search Events
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// sdk/useSearchEvents.ts
|
|
257
|
+
// Sends events to VTEX SP (search personalization):
|
|
258
|
+
// - search.query -- when user searches
|
|
259
|
+
// - search.autocomplete.query -- autocomplete queries
|
|
260
|
+
// - search.click -- when user clicks a search result
|
|
261
|
+
// - session.ping -- heartbeat
|
|
262
|
+
// - page.confirmation -- page view
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Cart / Wishlist / User Hooks
|
|
266
|
+
|
|
267
|
+
These come from the VTEX app (`apps/vtex/hooks/`):
|
|
268
|
+
|
|
269
|
+
### useCart
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { useCart } from "apps/vtex/hooks/useCart.ts";
|
|
273
|
+
|
|
274
|
+
const { cart, addItems, updateItems, loading } = useCart();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Provides reactive cart state with mutations. Automatically syncs with VTEX OrderForm API.
|
|
278
|
+
|
|
279
|
+
### useWishlist
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { useWishlist } from "apps/vtex/hooks/useWishlist.ts";
|
|
283
|
+
|
|
284
|
+
const { addItem, removeItem, getItem, loading } = useWishlist();
|
|
285
|
+
|
|
286
|
+
const isInWishlist = useComputed(() => !!getItem({ productId, skuId }));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### useUser
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { useUser } from "apps/vtex/hooks/useUser.ts";
|
|
293
|
+
|
|
294
|
+
const { user } = useUser();
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Sites often wrap this with a custom `useCheckUser` that caches in sessionStorage:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// hooks/useCheckUser.ts
|
|
301
|
+
export function useCheckUser() {
|
|
302
|
+
const cached = sessionStorage.getItem("user");
|
|
303
|
+
if (cached) return JSON.parse(cached);
|
|
304
|
+
const user = await invoke.site.actions.checkUser();
|
|
305
|
+
sessionStorage.setItem("user", JSON.stringify(user));
|
|
306
|
+
return user;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## SDK Utilities
|
|
311
|
+
|
|
312
|
+
Common SDK utilities found in production sites:
|
|
313
|
+
|
|
314
|
+
| Utility | Purpose |
|
|
315
|
+
|---------|---------|
|
|
316
|
+
| `useUI` | Global UI state signals (cart drawer, menu, search, wishlist modal) |
|
|
317
|
+
| `formatPrice` | `Intl.NumberFormat` with BRL/locale caching |
|
|
318
|
+
| `useOffer` | Extract price, listPrice, installments, availability from `AggregateOffer` |
|
|
319
|
+
| `useLazyLoad` | IntersectionObserver hook returning `[isVisible, setTarget]` |
|
|
320
|
+
| `useAddToCart` | Combines `useCart` + `useUI` + analytics for add-to-cart flow |
|
|
321
|
+
| `useSuggestions` | Debounced autocomplete via invoke + signals |
|
|
322
|
+
| `useVariantPossibilities` | Map product variants by spec (color, size) for selector UI |
|
|
323
|
+
| `useSizes` | Ordered size map (PP, P, M, G, GG) from variants |
|
|
324
|
+
| `useColumns` | Grid column count signals for PLP layout toggle |
|
|
325
|
+
| `useShowMore` | Pagination signals for infinite scroll |
|
|
326
|
+
| `useWindowSize` | Window dimensions with resize listener |
|
|
327
|
+
| `useIdRetrofit` | Unique ID generator (Fresh/Preact workaround) |
|
|
328
|
+
| `fetchStockBySku` | Batch SKU stock check via VTEX API |
|
|
329
|
+
|
|
330
|
+
## TanStack Migration Notes
|
|
331
|
+
|
|
332
|
+
When migrating these patterns to TanStack Start:
|
|
333
|
+
|
|
334
|
+
| Fresh/Preact Pattern | TanStack Equivalent |
|
|
335
|
+
|---------------------|---------------------|
|
|
336
|
+
| `invoke` proxy | Direct function imports + React Query |
|
|
337
|
+
| `@preact/signals` | `@tanstack/react-store` or `useState` |
|
|
338
|
+
| Islands | Regular React components (all hydrated) |
|
|
339
|
+
| `usePartialSection` | TanStack Router search params + React Query |
|
|
340
|
+
| `sendAnalyticsEvent` | Same pattern, use `useEffect` for load events |
|
|
341
|
+
| `useCart` / `useWishlist` | React Query mutations from `apps-start/vtex/hooks/` |
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# CMS Wiring
|
|
2
|
+
|
|
3
|
+
How the Deco CMS connects to the storefront via the decofile and `__resolveType` references.
|
|
4
|
+
|
|
5
|
+
## The Decofile (`.deco/blocks/`)
|
|
6
|
+
|
|
7
|
+
The CMS state lives in `.deco/blocks/` as JSON files. Each file is a "block" -- a configuration object that the resolution engine processes at runtime.
|
|
8
|
+
|
|
9
|
+
### File Types
|
|
10
|
+
|
|
11
|
+
| Pattern | Purpose | Example |
|
|
12
|
+
|---------|---------|---------|
|
|
13
|
+
| `site.json` | Root configuration (global sections, routes, SEO, theme) | Always one per site |
|
|
14
|
+
| `everyone.json` | Route definitions via flag (matches all requests) | URL-to-page mapping |
|
|
15
|
+
| `pages-*.json` | Page blocks with sections, variants, matchers | `pages-homeretrofit-ecosystem-2794ebefc7d8.json` |
|
|
16
|
+
| `redirects-*.json` | Individual redirect definitions | `redirects-masculino-33185.json` |
|
|
17
|
+
| `redirects-from-csv.json` | Bulk redirects from CSV file | One per import |
|
|
18
|
+
| `vtex.json` | VTEX app configuration (account, secrets) | One per VTEX site |
|
|
19
|
+
| `vtex_proxy.json` | VTEX proxy route configuration | Checkout/API proxy |
|
|
20
|
+
| `*.json` (other) | Reusable blocks (shelves, videos, banners) | `shelf-suggestions.json` |
|
|
21
|
+
|
|
22
|
+
## `__resolveType` -- The Wiring Mechanism
|
|
23
|
+
|
|
24
|
+
Every block has a `__resolveType` field that tells the engine which resolver (loader, section, handler, matcher) to use:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"__resolveType": "vtex/loaders/intelligentSearch/productListingPage.ts",
|
|
29
|
+
"query": "shoes",
|
|
30
|
+
"count": 12,
|
|
31
|
+
"sort": "OrderByScoreDESC"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The engine:
|
|
36
|
+
1. Finds the resolver registered for that key in the manifest
|
|
37
|
+
2. Resolves all nested `__resolveType` references in props (recursive)
|
|
38
|
+
3. Calls the resolver function with the resolved props
|
|
39
|
+
4. Returns the result
|
|
40
|
+
|
|
41
|
+
### Nesting Example
|
|
42
|
+
|
|
43
|
+
A section's props in the CMS can reference a loader:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"__resolveType": "site/sections/ProductRetrofit/ProductShelf.tsx",
|
|
48
|
+
"products": {
|
|
49
|
+
"__resolveType": "vtex/loaders/intelligentSearch/productListingPage.ts",
|
|
50
|
+
"query": "new arrivals",
|
|
51
|
+
"count": 8
|
|
52
|
+
},
|
|
53
|
+
"title": "New Arrivals"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The engine resolves `products` first (calls the VTEX loader), then passes the result to the section component as the `products` prop.
|
|
58
|
+
|
|
59
|
+
## `site.json` -- Root Configuration
|
|
60
|
+
|
|
61
|
+
The root block defines the site's global configuration:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"global": [
|
|
66
|
+
{ "__resolveType": "site/sections/Theme/Theme.tsx", "mainColors": { "base-100": "#FFFFFF" } },
|
|
67
|
+
{ "__resolveType": "site/sections/Analytics/Analytics.tsx", "trackingIds": ["GTM-XXXX"] },
|
|
68
|
+
{ "__resolveType": "site/sections/PromoBar/PromoBar.tsx", "text": "Free shipping" }
|
|
69
|
+
],
|
|
70
|
+
"routes": [
|
|
71
|
+
{ "__resolveType": "website/loaders/pages.ts" },
|
|
72
|
+
{ "__resolveType": "vtex/loaders/proxy.ts" },
|
|
73
|
+
{ "__resolveType": "website/loaders/redirectsFromCsv.ts", "from": "utils/redirects.csv" },
|
|
74
|
+
{ "__resolveType": "website/loaders/redirects.ts" }
|
|
75
|
+
],
|
|
76
|
+
"seo": {
|
|
77
|
+
"title": "My Store",
|
|
78
|
+
"description": "...",
|
|
79
|
+
"titleTemplate": "%s | My Store",
|
|
80
|
+
"image": "https://..."
|
|
81
|
+
},
|
|
82
|
+
"theme": { "__resolveType": "site/sections/Theme/Theme.tsx" },
|
|
83
|
+
"commerce": { "platform": "vtex" },
|
|
84
|
+
"__resolveType": "site/apps/site.ts"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Global Sections
|
|
89
|
+
|
|
90
|
+
`global` contains sections rendered on every page (analytics, promo bars, chat widgets). They're resolved once and injected into every response.
|
|
91
|
+
|
|
92
|
+
### Routes
|
|
93
|
+
|
|
94
|
+
`routes` defines the URL resolution order:
|
|
95
|
+
1. `website/loaders/pages.ts` -- CMS page matching
|
|
96
|
+
2. `vtex/loaders/proxy.ts` -- VTEX checkout/API proxy
|
|
97
|
+
3. `website/loaders/redirectsFromCsv.ts` -- bulk redirects from CSV
|
|
98
|
+
4. `website/loaders/redirects.ts` -- individual CMS redirect blocks
|
|
99
|
+
|
|
100
|
+
Order matters -- first match wins.
|
|
101
|
+
|
|
102
|
+
## `everyone.json` -- Route Definitions
|
|
103
|
+
|
|
104
|
+
The flag `$live/flags/everyone.ts` wraps route definitions that apply to all requests:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"__resolveType": "$live/flags/everyone.ts",
|
|
109
|
+
"true": {
|
|
110
|
+
"routes": [
|
|
111
|
+
{
|
|
112
|
+
"pathTemplate": "/",
|
|
113
|
+
"handler": {
|
|
114
|
+
"__resolveType": "$live/handlers/fresh.ts",
|
|
115
|
+
"page": { "__resolveType": "pages-homeretrofit-ecosystem-2794ebefc7d8" }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"pathTemplate": "/*",
|
|
120
|
+
"handler": {
|
|
121
|
+
"__resolveType": "$live/handlers/fresh.ts",
|
|
122
|
+
"page": { "__resolveType": "pages-category-42a3f" }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Each route maps a `pathTemplate` to a handler that references a page block.
|
|
131
|
+
|
|
132
|
+
## Page Blocks (`pages-*.json`)
|
|
133
|
+
|
|
134
|
+
Page blocks define which sections render on a page, with optional A/B testing via variants:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"name": "Home",
|
|
139
|
+
"path": "/",
|
|
140
|
+
"sections": {
|
|
141
|
+
"__resolveType": "website/flags/multivariate.ts",
|
|
142
|
+
"variants": [
|
|
143
|
+
{
|
|
144
|
+
"rule": { "__resolveType": "website/matchers/device.ts", "mobile": true },
|
|
145
|
+
"value": [
|
|
146
|
+
{ "__resolveType": "site/sections/HeroBannerRetrofit/HeroBanner.tsx", "image": "..." },
|
|
147
|
+
{ "__resolveType": "site/sections/ProductRetrofit/ProductShelf.tsx", "products": { "__resolveType": "vtex/loaders/..." } }
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"rule": { "__resolveType": "$live/matchers/MatchAlways.ts" },
|
|
152
|
+
"value": [
|
|
153
|
+
{ "__resolveType": "site/sections/HeroBannerRetrofit/HeroBanner.tsx", "image": "..." }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The resolver evaluates matchers in order. First matching variant wins. `MatchAlways` is the default fallback.
|
|
162
|
+
|
|
163
|
+
## Redirect System
|
|
164
|
+
|
|
165
|
+
### Individual Redirects
|
|
166
|
+
|
|
167
|
+
Each redirect is a separate block in `.deco/blocks/`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"redirect": {
|
|
172
|
+
"from": "/masculino",
|
|
173
|
+
"to": "/masculino-ver-todos",
|
|
174
|
+
"type": "temporary"
|
|
175
|
+
},
|
|
176
|
+
"__resolveType": "website/loaders/redirect.ts"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Bulk Redirects from CSV
|
|
181
|
+
|
|
182
|
+
The `redirects-from-csv.json` block references a CSV file:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"from": "utils/redirects.csv",
|
|
187
|
+
"forcePermanentRedirects": true,
|
|
188
|
+
"__resolveType": "website/loaders/redirectsFromCsv.ts"
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
CSV format: `from,to,type` (one redirect per line).
|
|
193
|
+
|
|
194
|
+
### Redirect Aggregation
|
|
195
|
+
|
|
196
|
+
`website/loaders/redirects.ts` in the routes array aggregates all individual `website/loaders/redirect.ts` blocks into a single redirect handler.
|
|
197
|
+
|
|
198
|
+
## Dependency Resolution via `ctx.get()`
|
|
199
|
+
|
|
200
|
+
Loaders and actions can resolve other blocks at runtime using `ctx.get()`:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
export const loader = async (props: Props, req: Request, ctx: AppContext) => {
|
|
204
|
+
const { credentials } = await ctx.get({
|
|
205
|
+
"__resolveType": "Tokens",
|
|
206
|
+
});
|
|
207
|
+
const { appKey, appToken } = credentials;
|
|
208
|
+
// Use VTEX API with credentials
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This pattern is used for:
|
|
213
|
+
- **Secrets** (API keys, tokens) -- resolved from CMS-managed Secret blocks
|
|
214
|
+
- **Configuration** -- any shared config block referenced by multiple loaders
|
|
215
|
+
- **Nested block resolution** -- composing data from multiple sources
|
|
216
|
+
|
|
217
|
+
## Admin Selector
|
|
218
|
+
|
|
219
|
+
When a section prop's TypeScript type matches a loader's return type, the admin shows a dropdown to select which loader provides the data. For example:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
interface Props {
|
|
223
|
+
page: ProductDetailsPage | null;
|
|
224
|
+
products: Product[] | null;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The admin knows that `ProductDetailsPage` can be produced by `vtex/loaders/intelligentSearch/productDetailsPage.ts` or `vtex/loaders/legacy/productDetailsPage.ts`, and shows both options.
|
|
229
|
+
|
|
230
|
+
This is powered by the schema system -- `generate-schema.ts` extracts prop types and the admin matches them against loader return types.
|