@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,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deco-tanstack-search
|
|
3
|
+
description: Implement and debug search functionality in Deco storefronts on TanStack Start. Covers the full search data flow from SearchBar to VTEX Intelligent Search API, including URL parameter propagation, CMS page resolution, filter toggling, pagination, and common pitfalls.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deco TanStack Search
|
|
7
|
+
|
|
8
|
+
Complete reference for implementing search in Deco storefronts running on TanStack Start / React / Cloudflare Workers.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Implementing or debugging search (`/s?q=...`) pages
|
|
13
|
+
- Fixing "search returns no results" or "search page shows 404"
|
|
14
|
+
- Adding filter support to PLP/search pages
|
|
15
|
+
- Debugging pagination or sort not working
|
|
16
|
+
- Porting search from Fresh/Deno to TanStack Start
|
|
17
|
+
- Understanding how URL parameters flow from the browser to VTEX Intelligent Search API
|
|
18
|
+
|
|
19
|
+
## Architecture: The Search Data Flow
|
|
20
|
+
|
|
21
|
+
The search flow spans **four layers**. Understanding each layer is critical for debugging.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ 1. BROWSER — SearchBar component │
|
|
26
|
+
│ User types "telha" → form submits │
|
|
27
|
+
│ navigate({ to: "/s", search: { q: "telha" } }) │
|
|
28
|
+
│ URL becomes: /s?q=telha │
|
|
29
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
30
|
+
│ 2. TANSTACK ROUTER — cmsRouteConfig in $.tsx │
|
|
31
|
+
│ loaderDeps extracts search params: { q: "telha" } │
|
|
32
|
+
│ loader builds fullPath: "/s?q=telha" │
|
|
33
|
+
│ Calls loadCmsPage({ data: "/s?q=telha" }) │
|
|
34
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
35
|
+
│ 3. @decocms/start — CMS resolution pipeline │
|
|
36
|
+
│ findPageByPath("/s") → matches CMS page with path: "/s" │
|
|
37
|
+
│ matcherCtx.url = "http://localhost:5173/s?q=telha" │
|
|
38
|
+
│ resolve.ts injects __pagePath="/s" and __pageUrl="...?q=telha"│
|
|
39
|
+
│ into commerce loader props │
|
|
40
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
41
|
+
│ 4. @decocms/apps — VTEX productListingPage loader │
|
|
42
|
+
│ Reads query from: props.query ?? __pageUrl.searchParams("q") │
|
|
43
|
+
│ Reads sort from: props.sort ?? __pageUrl.searchParams("sort") │
|
|
44
|
+
│ Reads page from: props.page ?? __pageUrl.searchParams("page") │
|
|
45
|
+
│ Reads filters from: __pageUrl filter.* params │
|
|
46
|
+
│ Calls VTEX Intelligent Search API │
|
|
47
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Layer 1: SearchBar Component
|
|
51
|
+
|
|
52
|
+
### Correct Pattern (TanStack Router)
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { useNavigate, Link } from "@tanstack/react-router";
|
|
56
|
+
|
|
57
|
+
function Searchbar({ action = "/s", name = "q" }) {
|
|
58
|
+
const navigate = useNavigate();
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<form
|
|
62
|
+
action={action}
|
|
63
|
+
onSubmit={(e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
const q = new FormData(e.currentTarget).get(name)?.toString();
|
|
66
|
+
if (q) navigate({ to: action, search: { q } });
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<input name={name} placeholder="Buscar..." />
|
|
70
|
+
<button type="submit">Buscar</button>
|
|
71
|
+
</form>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Suggestion Links — Correct vs Wrong
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// WRONG — query string embedded in path, TanStack Router doesn't parse it
|
|
80
|
+
<Link to={`/s?q=${query}`}>Buscar por "{query}"</Link>
|
|
81
|
+
|
|
82
|
+
// CORRECT — search params as separate object
|
|
83
|
+
<Link to="/s" search={{ q: query }}>Buscar por "{query}"</Link>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Why it matters**: TanStack Router treats `to` as a path. If you embed `?q=telha` in the path, the router navigates to a path literally named `/s?q=telha` instead of `/s` with search param `q=telha`. The `loaderDeps` function receives an empty `search` object and no query reaches the API.
|
|
87
|
+
|
|
88
|
+
### Autocomplete / Suggestion Links
|
|
89
|
+
|
|
90
|
+
Category/product suggestion links should also use TanStack Router `<Link>`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
{autocomplete.map(({ name, slug }) => (
|
|
94
|
+
<Link to={`/${slug}`} preload="intent">{name}</Link>
|
|
95
|
+
))}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Layer 2: Route Configuration ($.tsx)
|
|
99
|
+
|
|
100
|
+
The catch-all route uses `cmsRouteConfig` from `@decocms/start/routes`:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// src/routes/$.tsx
|
|
104
|
+
import { createFileRoute, notFound } from "@tanstack/react-router";
|
|
105
|
+
import { cmsRouteConfig, loadDeferredSection } from "@decocms/start/routes";
|
|
106
|
+
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
107
|
+
|
|
108
|
+
const config = cmsRouteConfig({
|
|
109
|
+
siteName: "My Store",
|
|
110
|
+
defaultTitle: "My Store - Products",
|
|
111
|
+
ignoreSearchParams: ["skuId"], // Do NOT ignore: q, sort, page, filter.*
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const Route = createFileRoute("/$")({
|
|
115
|
+
loaderDeps: config.loaderDeps,
|
|
116
|
+
loader: async (ctx) => {
|
|
117
|
+
const page = await config.loader(ctx);
|
|
118
|
+
if (!page) throw notFound();
|
|
119
|
+
return page;
|
|
120
|
+
},
|
|
121
|
+
component: CmsPage,
|
|
122
|
+
// ...
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Critical: ignoreSearchParams
|
|
127
|
+
|
|
128
|
+
`ignoreSearchParams` controls which URL params are excluded from `loaderDeps`. When a param is ignored, changing it does NOT trigger a server re-fetch.
|
|
129
|
+
|
|
130
|
+
**Never ignore**: `q`, `sort`, `page`, `fuzzy`, any `filter.*` param.
|
|
131
|
+
|
|
132
|
+
**Safe to ignore**: `skuId` (variant selection is client-side), `utm_*`, `gclid`.
|
|
133
|
+
|
|
134
|
+
### How loaderDeps works
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
loaderDeps: ({ search }) => {
|
|
138
|
+
// search = { q: "telha", sort: "price:asc" }
|
|
139
|
+
// After filtering ignoreSearchParams:
|
|
140
|
+
return { search: { q: "telha", sort: "price:asc" } };
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The loader receives `deps.search` and builds the full path:
|
|
145
|
+
```ts
|
|
146
|
+
const basePath = "/" + (params._splat || ""); // "/s"
|
|
147
|
+
const searchStr = "?" + new URLSearchParams(deps.search).toString(); // "?q=telha&sort=price:asc"
|
|
148
|
+
loadCmsPage({ data: "/s?q=telha&sort=price:asc" });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Layer 3: CMS Resolution (@decocms/start)
|
|
152
|
+
|
|
153
|
+
### Page Matching
|
|
154
|
+
|
|
155
|
+
`findPageByPath("/s")` searches CMS blocks for a page with `path: "/s"`.
|
|
156
|
+
|
|
157
|
+
**Prerequisite**: The CMS must have a page block with `"path": "/s"` — typically `pages-search-*.json` in `.deco/blocks/`.
|
|
158
|
+
|
|
159
|
+
If the page block is missing from `blocks.gen.ts`, search will 404. Regenerate with:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm run generate:blocks
|
|
163
|
+
# or
|
|
164
|
+
npx tsx node_modules/@decocms/start/scripts/generate-blocks.ts
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### __pageUrl Injection
|
|
168
|
+
|
|
169
|
+
In `resolve.ts`, when a commerce loader is called:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
if (rctx.matcherCtx.path) {
|
|
173
|
+
resolvedProps.__pagePath = rctx.matcherCtx.path; // "/s"
|
|
174
|
+
}
|
|
175
|
+
if (rctx.matcherCtx.url) {
|
|
176
|
+
resolvedProps.__pageUrl = rctx.matcherCtx.url; // "http://localhost:5173/s?q=telha"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This is how the request URL reaches the commerce loader — not via `Request` object (as in Fresh), but via injected props.
|
|
181
|
+
|
|
182
|
+
## Layer 4: Commerce Loader (VTEX)
|
|
183
|
+
|
|
184
|
+
### The productListingPage Loader
|
|
185
|
+
|
|
186
|
+
The loader must read search parameters from `__pageUrl` as fallback when `props.query` is not set by the CMS:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
export interface PLPProps {
|
|
190
|
+
query?: string;
|
|
191
|
+
count?: number;
|
|
192
|
+
sort?: string;
|
|
193
|
+
fuzzy?: string;
|
|
194
|
+
page?: number;
|
|
195
|
+
selectedFacets?: SelectedFacet[];
|
|
196
|
+
hideUnavailableItems?: boolean;
|
|
197
|
+
__pagePath?: string;
|
|
198
|
+
__pageUrl?: string; // ← CRITICAL: must be declared
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default async function vtexProductListingPage(props: PLPProps) {
|
|
202
|
+
const pageUrl = props.__pageUrl
|
|
203
|
+
? new URL(props.__pageUrl, "https://localhost")
|
|
204
|
+
: null;
|
|
205
|
+
|
|
206
|
+
// Read from props first (CMS override), then URL (runtime), then default
|
|
207
|
+
const query = props.query ?? pageUrl?.searchParams.get("q") ?? "";
|
|
208
|
+
const count = Number(pageUrl?.searchParams.get("PS") ?? props.count ?? 12);
|
|
209
|
+
const sort = props.sort || pageUrl?.searchParams.get("sort") || "";
|
|
210
|
+
const fuzzy = props.fuzzy ?? pageUrl?.searchParams.get("fuzzy") ?? undefined;
|
|
211
|
+
const pageFromUrl = pageUrl?.searchParams.get("page");
|
|
212
|
+
const page = props.page ?? (pageFromUrl ? Number(pageFromUrl) - 1 : 0);
|
|
213
|
+
// ...
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Filter Extraction from URL
|
|
218
|
+
|
|
219
|
+
Users apply filters via URL params like `?filter.category-1=telhas&filter.brand=saint-gobain`. The loader must parse these:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
if (pageUrl) {
|
|
223
|
+
for (const [name, value] of pageUrl.searchParams.entries()) {
|
|
224
|
+
const dotIndex = name.indexOf(".");
|
|
225
|
+
if (dotIndex > 0 && name.slice(0, dotIndex) === "filter") {
|
|
226
|
+
const key = name.slice(dotIndex + 1);
|
|
227
|
+
if (key && !facets.some((f) => f.key === key && f.value === value)) {
|
|
228
|
+
facets.push({ key, value });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Pagination Links Must Preserve URL Params
|
|
236
|
+
|
|
237
|
+
When building `nextPage`/`previousPage` URLs, persist all current URL params (q, sort, filter.*) and only change the page number:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const paramsToPersist = new URLSearchParams();
|
|
241
|
+
if (pageUrl) {
|
|
242
|
+
for (const [k, v] of pageUrl.searchParams.entries()) {
|
|
243
|
+
if (k !== "page" && k !== "PS" && !k.startsWith("filter.")) {
|
|
244
|
+
paramsToPersist.append(k, v);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
if (query) paramsToPersist.set("q", query);
|
|
249
|
+
if (sort) paramsToPersist.set("sort", sort);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Filter toggle URLs also need paramsToPersist
|
|
253
|
+
const filters = visibleFacets.map(toFilter(facets, paramsToPersist));
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Key Difference: Fresh/Deno vs TanStack Start
|
|
257
|
+
|
|
258
|
+
| Aspect | Fresh/Deno (original) | TanStack Start |
|
|
259
|
+
|--------|----------------------|----------------|
|
|
260
|
+
| **Request access** | Loader receives `Request` directly | Loader receives CMS-resolved props |
|
|
261
|
+
| **URL reading** | `url.searchParams.get("q")` | `props.__pageUrl` → parse URL |
|
|
262
|
+
| **Navigation** | `<a href="/s?q=...">` (full reload) | `navigate({ to: "/s", search: { q } })` (SPA) |
|
|
263
|
+
| **Route matching** | Deco runtime matches `/s` | TanStack catch-all `/$` + `cmsRouteConfig` |
|
|
264
|
+
| **Param flow** | Direct from Request | URL → loaderDeps → loadCmsPage → matcherCtx → resolve.ts → __pageUrl |
|
|
265
|
+
|
|
266
|
+
## CMS Page Block Structure
|
|
267
|
+
|
|
268
|
+
The search page block (`.deco/blocks/pages-search-*.json`) should look like:
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"name": "Search",
|
|
273
|
+
"path": "/s",
|
|
274
|
+
"sections": [
|
|
275
|
+
{
|
|
276
|
+
"page": {
|
|
277
|
+
"sort": "",
|
|
278
|
+
"count": 12,
|
|
279
|
+
"fuzzy": "automatic",
|
|
280
|
+
"__resolveType": "vtex/loaders/intelligentSearch/productListingPage.ts",
|
|
281
|
+
"selectedFacets": []
|
|
282
|
+
},
|
|
283
|
+
"__resolveType": "site/sections/Product/SearchResult.tsx"
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"__resolveType": "website/pages/Page.tsx"
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Important**: The `page.query` field is intentionally empty/absent. The query comes from the URL at runtime via `__pageUrl`.
|
|
291
|
+
|
|
292
|
+
## Debugging Checklist
|
|
293
|
+
|
|
294
|
+
When search is broken, check each layer:
|
|
295
|
+
|
|
296
|
+
### 1. Is the URL correct?
|
|
297
|
+
```
|
|
298
|
+
Expected: /s?q=telha
|
|
299
|
+
Check: Browser address bar after search submit
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 2. Are search params reaching loaderDeps?
|
|
303
|
+
Add a temporary log in `$.tsx`:
|
|
304
|
+
```ts
|
|
305
|
+
loader: async (ctx) => {
|
|
306
|
+
console.log("[CMS Route] deps:", ctx.deps);
|
|
307
|
+
// Should show: { search: { q: "telha" } }
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 3. Does the CMS page exist?
|
|
312
|
+
```bash
|
|
313
|
+
# Check blocks.gen.ts has the search page
|
|
314
|
+
grep '"path": "/s"' src/server/cms/blocks.gen.ts
|
|
315
|
+
# If missing, regenerate:
|
|
316
|
+
npm run generate:blocks
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 4. Is __pageUrl being injected?
|
|
320
|
+
Add a temporary log in the commerce loader:
|
|
321
|
+
```ts
|
|
322
|
+
console.log("[PLP] __pageUrl:", props.__pageUrl);
|
|
323
|
+
// Should show: "http://localhost:5173/s?q=telha"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### 5. Is the query reaching VTEX API?
|
|
327
|
+
Check terminal output for the Intelligent Search API call:
|
|
328
|
+
```
|
|
329
|
+
[vtex] GET .../api/io/_v/api/intelligent-search/product_search/?query=telha&...
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 6. Is the loader returning null?
|
|
333
|
+
The loader returns `null` when both `facets` and `query` are empty:
|
|
334
|
+
```ts
|
|
335
|
+
if (!facets.length && !query) {
|
|
336
|
+
return null; // ← This triggers "no results" / NotFound
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Common Pitfalls
|
|
341
|
+
|
|
342
|
+
### 1. Query string in Link `to` prop
|
|
343
|
+
```tsx
|
|
344
|
+
// BUG: TanStack Router doesn't parse ?q= from `to`
|
|
345
|
+
<Link to={`/s?q=${query}`}>
|
|
346
|
+
// FIX: Use search prop
|
|
347
|
+
<Link to="/s" search={{ q: query }}>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 2. Missing __pageUrl in loader interface
|
|
351
|
+
If `PLPProps` doesn't declare `__pageUrl`, TypeScript won't complain (it's injected dynamically), but the loader won't read it.
|
|
352
|
+
|
|
353
|
+
### 3. blocks.gen.ts out of date
|
|
354
|
+
After adding/editing CMS blocks locally, `blocks.gen.ts` must be regenerated. Automate in `package.json`:
|
|
355
|
+
```json
|
|
356
|
+
"dev": "npm run generate:blocks && vite dev"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 4. ignoreSearchParams filtering out q
|
|
360
|
+
If `ignoreSearchParams` includes `"q"`, search will never work. Only ignore client-side-only params.
|
|
361
|
+
|
|
362
|
+
### 5. Shopify vs VTEX patterns
|
|
363
|
+
Shopify loader already reads `__pageUrl` correctly:
|
|
364
|
+
```ts
|
|
365
|
+
const query = props.query || pageUrl.searchParams.get("q") || "";
|
|
366
|
+
```
|
|
367
|
+
VTEX initially didn't — this was the root cause of the espacosmart search bug.
|
|
368
|
+
|
|
369
|
+
### 6. Duplicate search param keys (filters)
|
|
370
|
+
|
|
371
|
+
VTEX filter URLs use duplicate keys: `?filter.category-1=telhas&filter.category-1=pisos`.
|
|
372
|
+
TanStack Router's `search` is a plain `Record<string, string>` — it **cannot represent duplicate keys**.
|
|
373
|
+
|
|
374
|
+
**Consequences**:
|
|
375
|
+
- `navigate({ search: Object.fromEntries(params) })` loses all but the last value per key
|
|
376
|
+
- `loaderDeps` receives a flat object, so the `loader` builds a URL with collapsed params
|
|
377
|
+
|
|
378
|
+
**Solution**: Use plain `<a href={url}>` for filter and pagination links. This triggers a server round-trip (like the original Fresh site), but the **real** request URL preserves all params. The `cmsRoute.ts` `loadCmsPageInternal` prefers `getRequestUrl()` over the `loaderDeps`-built path, so the commerce loader receives the full URL via `__pageUrl`.
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
// WRONG — navigate({search}) collapses duplicate keys
|
|
382
|
+
<Link to="." search={parsedParams}>Filter</Link>
|
|
383
|
+
|
|
384
|
+
// WRONG — TanStack Router treats `to` as a path, not a relative URL
|
|
385
|
+
<Link to="?filter.category-1=telhas&q=telha">Filter</Link>
|
|
386
|
+
|
|
387
|
+
// CORRECT — plain <a href> preserves full query string
|
|
388
|
+
<a href="?filter.category-1=telhas&q=telha">Filter</a>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Sort** (single key) can safely use `navigate({ search })` since there are no duplicate keys.
|
|
392
|
+
|
|
393
|
+
## Related Skills
|
|
394
|
+
|
|
395
|
+
- `deco-tanstack-storefront-patterns` — General runtime patterns for TanStack storefronts
|
|
396
|
+
- `deco-apps-vtex-porting` — Full guide for porting VTEX loaders to apps-start
|
|
397
|
+
- `deco-tanstack-navigation` — Navigation patterns including Link, prefetch, search params
|
|
398
|
+
- `deco-tanstack-hydration-fixes` — Fixing hydration and flash-of-white issues
|
|
399
|
+
- `deco-cms-route-config` — Deep dive into cmsRouteConfig and route helpers
|
|
400
|
+
|
|
401
|
+
## Files Reference
|
|
402
|
+
|
|
403
|
+
| File | Layer | Purpose |
|
|
404
|
+
|------|-------|---------|
|
|
405
|
+
| `src/components/search/SearchBar.tsx` | Browser | Search input, form submit, suggestion links |
|
|
406
|
+
| `src/routes/$.tsx` | Router | Catch-all route with `cmsRouteConfig` |
|
|
407
|
+
| `deco-start/src/routes/cmsRoute.ts` | Framework | `loaderDeps`, `loadCmsPage`, URL construction |
|
|
408
|
+
| `deco-start/src/cms/resolve.ts` | Framework | `__pageUrl`/`__pagePath` injection into loaders |
|
|
409
|
+
| `apps-start/vtex/inline-loaders/productListingPage.ts` | Commerce | VTEX IS API call, URL param reading |
|
|
410
|
+
| `.deco/blocks/pages-search-*.json` | CMS | Page definition for `/s` route |
|
|
411
|
+
| `src/server/cms/blocks.gen.ts` | Build | Compiled CMS blocks (must include search page) |
|