@decocms/start 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,681 @@
1
+ ---
2
+ name: deco-tanstack-navigation
3
+ description: "Complete guide for migrating Fresh/Deno navigation to TanStack Router in Deco storefronts. Covers: replacing <a href> with <Link>, prefetch strategies for instant navigation, type-safe params, activeProps for menus, search state as URL source of truth, SSR-first SEO architecture, loaderDeps for reactive search params, form submissions via server actions, and programmatic preloading. Use when porting any Deco site from Fresh to TanStack Start."
4
+ ---
5
+
6
+ # Deco TanStack Navigation Migration
7
+
8
+ Complete playbook for replacing Fresh/Deno navigation with TanStack Router in Deco storefronts. Goes beyond simple `<a>` to `<Link>` — covers the full power of the router to build sites that feel like native apps while keeping SSR-first SEO.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Migrating a Fresh/Deno storefront to TanStack Start
13
+ - Links cause full page reloads instead of SPA transitions
14
+ - Filters, sort, or search reload the entire page
15
+ - Forms submit via GET and append query params
16
+ - Navigation feels slow (no prefetching)
17
+ - Menus don't highlight the active page
18
+ - Need type-safe route params
19
+ - Want URL as the single source of truth for filters/pagination
20
+
21
+ ---
22
+
23
+ ## Architecture: SSR-First, Hydrate Smart
24
+
25
+ ```
26
+ Request → Server
27
+ ├─ TanStack Router matches route
28
+ ├─ Route loader runs on server (createServerFn)
29
+ │ ├─ resolveDecoPage(path)
30
+ │ ├─ runSectionLoaders(sections, request)
31
+ │ └─ Return full page data
32
+ ├─ React renders to HTML (SSR)
33
+ └─ Response: full HTML + serialized data
34
+
35
+ Client receives HTML
36
+ ├─ Instantly visible (SEO, LCP, FCP)
37
+ ├─ React hydrates (attaches event handlers)
38
+ ├─ TanStack Router takes over navigation
39
+ └─ Subsequent navigations:
40
+ ├─ Prefetch on hover/intent (data + component)
41
+ ├─ Client-side render (no full page reload)
42
+ ├─ Only the changed route re-renders
43
+ └─ Shared layout (header/footer) stays mounted
44
+ ```
45
+
46
+ This gives you:
47
+ - **SEO**: Full HTML on first request, crawlers see everything
48
+ - **Speed**: Prefetch makes subsequent pages feel instant
49
+ - **State**: Cart, menus, form inputs survive navigation
50
+ - **Bandwidth**: Only route data transfers, not the full HTML shell
51
+
52
+ ---
53
+
54
+ ## Pattern 1: `<a href>` to `<Link>` with Prefetch
55
+
56
+ ### The Basic Migration
57
+
58
+ ```typescript
59
+ // FRESH — full page reload on every click
60
+ <a href={url}>Click me</a>
61
+
62
+ // TANSTACK — SPA navigation, preserves state
63
+ import { Link } from "@tanstack/react-router";
64
+ <Link to={url}>Click me</Link>
65
+ ```
66
+
67
+ ### Prefetch: Make Navigation Instant
68
+
69
+ The killer feature. The router can **preload the next page before the user clicks**.
70
+
71
+ ```typescript
72
+ // Preload when user hovers or focuses the link
73
+ <Link to="/produtos" preload="intent">
74
+ Produtos
75
+ </Link>
76
+
77
+ // Preload immediately when the link renders (good for hero CTAs)
78
+ <Link to="/ofertas" preload="render">
79
+ Ver Ofertas
80
+ </Link>
81
+
82
+ // Disable prefetch (for low-priority links)
83
+ <Link to="/termos" preload={false}>
84
+ Termos de Uso
85
+ </Link>
86
+ ```
87
+
88
+ **What gets preloaded:**
89
+ 1. Route component code (the JS chunk)
90
+ 2. Route loader data (the `createServerFn` call)
91
+ 3. Any nested route data
92
+
93
+ When the user clicks, everything is already cached — **navigation is instant**.
94
+
95
+ ### Prefetch Strategy by Component
96
+
97
+ | Component | Strategy | Why |
98
+ |-----------|----------|-----|
99
+ | Product card | `preload="intent"` | User will likely click after hover |
100
+ | NavItem (menu) | `preload="intent"` | High-intent interaction |
101
+ | Category link | `preload="intent"` | Top-of-funnel navigation |
102
+ | Hero CTA | `preload="render"` | Guaranteed next action |
103
+ | Breadcrumb | `preload="intent"` | Medium priority |
104
+ | Footer links | `preload={false}` | Rarely clicked |
105
+ | Filter options | N/A (use `useNavigate`) | Same page, different params |
106
+
107
+ ### When NOT to Replace `<a>`
108
+
109
+ Keep native `<a href>` for:
110
+ - External links (`https://...` to other domains)
111
+ - Checkout redirects (VTEX checkout is on a different domain)
112
+ - Download links (href pointing to files)
113
+ - Anchor links (`#section-id`)
114
+ - `mailto:` / `tel:` links
115
+
116
+ ### Discovery Command
117
+
118
+ ```bash
119
+ rg '<a\s+href=' src/components/ src/sections/ --glob '*.tsx' -l
120
+ ```
121
+
122
+ ### Gotcha: VTEX URLs Are Absolute
123
+
124
+ VTEX APIs return absolute URLs. Always convert:
125
+
126
+ ```typescript
127
+ import { relative } from "@decocms/apps/commerce/sdk/url";
128
+
129
+ <Link to={relative(product.url) ?? product.url} preload="intent">
130
+ {product.name}
131
+ </Link>
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Pattern 2: Type-Safe Params
137
+
138
+ TanStack Router generates types from your route tree. Use them.
139
+
140
+ ### Route Definition
141
+
142
+ ```typescript
143
+ // src/routes/produto/$slug.tsx
144
+ export const Route = createFileRoute("/produto/$slug")({
145
+ loader: async ({ params }) => {
146
+ // params.slug is typed as string — guaranteed by the router
147
+ const product = await loadProduct({ data: params.slug });
148
+ if (!product) throw notFound();
149
+ return product;
150
+ },
151
+ component: ProductPage,
152
+ });
153
+ ```
154
+
155
+ ### Linking with Type Safety
156
+
157
+ ```typescript
158
+ // TypeScript catches wrong params at compile time
159
+ <Link to="/produto/$slug" params={{ slug: product.slug }}>
160
+ {product.name}
161
+ </Link>
162
+
163
+ // ERROR: 'id' does not exist in type { slug: string }
164
+ <Link to="/produto/$slug" params={{ id: "123" }}>
165
+ ```
166
+
167
+ ### For Deco CMS Routes (Catch-All)
168
+
169
+ Deco sites use a catch-all route `/$` that resolves CMS pages. Links to CMS pages use plain paths:
170
+
171
+ ```typescript
172
+ <Link to={`/${categorySlug}`} preload="intent">
173
+ {category.name}
174
+ </Link>
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Pattern 3: `activeProps` for Menus
180
+
181
+ Automatically style the current page link.
182
+
183
+ ### Basic Usage
184
+
185
+ ```typescript
186
+ <Link
187
+ to="/dashboard"
188
+ activeProps={{ className: "font-bold text-primary border-b-2 border-primary" }}
189
+ inactiveProps={{ className: "text-base-content/60" }}
190
+ >
191
+ Dashboard
192
+ </Link>
193
+ ```
194
+
195
+ ### Navigation Menu (Real Example)
196
+
197
+ ```typescript
198
+ function NavItem({ href, label }: { href: string; label: string }) {
199
+ return (
200
+ <Link
201
+ to={href}
202
+ preload="intent"
203
+ activeProps={{ className: "text-primary font-bold" }}
204
+ activeOptions={{ exact: false }}
205
+ className="text-sm hover:text-primary transition-colors"
206
+ >
207
+ {label}
208
+ </Link>
209
+ );
210
+ }
211
+
212
+ function NavBar({ items }: { items: Array<{ href: string; label: string }> }) {
213
+ return (
214
+ <nav className="flex gap-4">
215
+ {items.map((item) => (
216
+ <NavItem key={item.href} {...item} />
217
+ ))}
218
+ </nav>
219
+ );
220
+ }
221
+ ```
222
+
223
+ ### `activeOptions`
224
+
225
+ ```typescript
226
+ activeOptions={{
227
+ exact: true, // Only active on exact path match (not children)
228
+ includeSearch: true, // Include search params in matching
229
+ }}
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Pattern 4: Search State as URL Source of Truth
235
+
236
+ Instead of managing filter/sort/pagination state in React state or signals, use the **URL as the single source of truth**.
237
+
238
+ ### The Problem with React State for Filters
239
+
240
+ ```typescript
241
+ // BAD: State is lost on page refresh, not shareable, no back-button support
242
+ const [sort, setSort] = useState("price:asc");
243
+ const [filters, setFilters] = useState({});
244
+ const [page, setPage] = useState(1);
245
+ ```
246
+
247
+ ### The TanStack Way: URL = State
248
+
249
+ ```typescript
250
+ // Link that preserves existing search params and adds/changes one
251
+ <Link
252
+ to="."
253
+ search={(prev) => ({
254
+ ...prev,
255
+ page: 2,
256
+ })}
257
+ >
258
+ Próxima página
259
+ </Link>
260
+
261
+ // Link that adds a filter
262
+ <Link
263
+ to="."
264
+ search={(prev) => ({
265
+ ...prev,
266
+ "filter.brand": "espacosmart",
267
+ })}
268
+ preload="intent"
269
+ >
270
+ Espaço Smart
271
+ </Link>
272
+
273
+ // Link that changes sort while keeping filters
274
+ <Link
275
+ to="."
276
+ search={(prev) => ({
277
+ ...prev,
278
+ sort: "price:asc",
279
+ })}
280
+ >
281
+ Menor Preço
282
+ </Link>
283
+ ```
284
+
285
+ ### Benefits
286
+
287
+ 1. **Shareable**: Copy URL → paste → same exact view
288
+ 2. **Back button**: Browser history just works
289
+ 3. **SEO**: Crawlers see the filter/sort URLs
290
+ 4. **SSR**: Server renders the correct results on first load
291
+ 5. **No state management needed**: No Zustand, no signals, no context
292
+
293
+ ### Reading Search Params in Components
294
+
295
+ ```typescript
296
+ function SearchResult() {
297
+ const { sort, q, page } = Route.useSearch();
298
+ // sort, q, page are typed based on route validation
299
+ }
300
+ ```
301
+
302
+ ### Validating Search Params (Advanced)
303
+
304
+ ```typescript
305
+ import { z } from "zod";
306
+
307
+ const searchSchema = z.object({
308
+ q: z.string().optional(),
309
+ sort: z.enum(["price:asc", "price:desc", "name:asc", "relevance:desc"]).optional(),
310
+ page: z.number().int().positive().optional().default(1),
311
+ "filter.brand": z.string().optional(),
312
+ "filter.price": z.string().optional(),
313
+ });
314
+
315
+ export const Route = createFileRoute("/s")({
316
+ validateSearch: searchSchema,
317
+ loaderDeps: ({ search }) => search,
318
+ loader: async ({ deps }) => {
319
+ return loadSearchResults({ data: deps });
320
+ },
321
+ });
322
+ ```
323
+
324
+ Now search params are **type-safe** and **validated**.
325
+
326
+ ---
327
+
328
+ ## Pattern 5: `window.location` Mutations to `useNavigate`
329
+
330
+ ### Problem
331
+
332
+ ```typescript
333
+ // FRESH — forces full page reload
334
+ window.location.search = params.toString();
335
+ window.location.href = newUrl;
336
+ globalThis.window.location.search = params.toString();
337
+ ```
338
+
339
+ ### Solution
340
+
341
+ ```typescript
342
+ import { useNavigate } from "@tanstack/react-router";
343
+
344
+ function Sort() {
345
+ const navigate = useNavigate();
346
+
347
+ const applySort = (e: React.ChangeEvent<HTMLSelectElement>) => {
348
+ const params = new URLSearchParams(window.location.search);
349
+ params.set("sort", e.currentTarget.value);
350
+ navigate({ search: Object.fromEntries(params) });
351
+ };
352
+ }
353
+ ```
354
+
355
+ ### With Debounce (Price Range Sliders)
356
+
357
+ ```typescript
358
+ const navigate = useNavigate();
359
+ const debounceRef = useRef<ReturnType<typeof setTimeout>>();
360
+
361
+ const applyPrice = (min: number, max: number) => {
362
+ clearTimeout(debounceRef.current);
363
+ debounceRef.current = setTimeout(() => {
364
+ const params = new URLSearchParams(window.location.search);
365
+ params.set("filter.price", `${min}:${max}`);
366
+ navigate({ search: Object.fromEntries(params) });
367
+ }, 500);
368
+ };
369
+ ```
370
+
371
+ ### Discovery
372
+
373
+ ```bash
374
+ rg 'window\.location\.(search|href)\s*=' src/ --glob '*.{tsx,ts}'
375
+ rg 'globalThis\.window\.location' src/ --glob '*.{tsx,ts}'
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Pattern 6: Form Submissions
381
+
382
+ ### Search Forms (Navigate with Query Params)
383
+
384
+ ```typescript
385
+ import { useNavigate } from "@tanstack/react-router";
386
+
387
+ function SearchForm({ action = "/s", name = "q" }) {
388
+ const navigate = useNavigate();
389
+
390
+ return (
391
+ <form
392
+ action={action}
393
+ onSubmit={(e) => {
394
+ e.preventDefault();
395
+ const q = new FormData(e.currentTarget).get(name)?.toString();
396
+ if (q) navigate({ to: action, search: { q } });
397
+ }}
398
+ >
399
+ <input name={name} />
400
+ <button type="submit">Search</button>
401
+ </form>
402
+ );
403
+ }
404
+ ```
405
+
406
+ Keep `action` as fallback for no-JS/crawlers.
407
+
408
+ ### Action Forms (Server Mutations)
409
+
410
+ Forms that POST data (newsletter, contact, shipping calc) use `createServerFn`:
411
+
412
+ ```typescript
413
+ import { createDocument } from "~/lib/vtex-actions-server";
414
+
415
+ function Newsletter() {
416
+ const [loading, setLoading] = useState(false);
417
+ const [message, setMessage] = useState("");
418
+
419
+ return (
420
+ <form onSubmit={async (e) => {
421
+ e.preventDefault();
422
+ const email = new FormData(e.currentTarget).get("email")?.toString();
423
+ if (!email) return;
424
+ try {
425
+ setLoading(true);
426
+ await createDocument({ data: { entity: "NW", dataForm: { email } } });
427
+ setMessage("Cadastrado com sucesso!");
428
+ } catch (err: any) {
429
+ setMessage("Erro: " + err.message);
430
+ } finally {
431
+ setLoading(false);
432
+ setTimeout(() => setMessage(""), 3000);
433
+ }
434
+ }}>
435
+ <input name="email" type="email" required />
436
+ <button type="submit" disabled={loading}>
437
+ {loading ? "Enviando..." : "Inscrever"}
438
+ </button>
439
+ {message && <p>{message}</p>}
440
+ </form>
441
+ );
442
+ }
443
+ ```
444
+
445
+ ---
446
+
447
+ ## Pattern 7: Route `loaderDeps` for Reactive Search Params
448
+
449
+ ### Problem
450
+
451
+ After converting to `useNavigate`, the URL changes but the page content doesn't update.
452
+
453
+ ### Root Cause
454
+
455
+ TanStack Router only re-runs a loader when its **dependencies** change. By default: path params only, NOT search params.
456
+
457
+ ### Solution
458
+
459
+ ```typescript
460
+ export const Route = createFileRoute("/$")({
461
+ loaderDeps: ({ search }) => ({ search }),
462
+
463
+ loader: async ({ params, deps }) => {
464
+ const basePath = "/" + (params._splat || "");
465
+ const searchStr = deps.search
466
+ ? "?" + new URLSearchParams(deps.search as Record<string, string>).toString()
467
+ : "";
468
+
469
+ const page = await loadCmsPage({ data: basePath + searchStr });
470
+ if (!page) throw notFound();
471
+ return page;
472
+ },
473
+ });
474
+ ```
475
+
476
+ ### Pass Search Params to Section Loaders
477
+
478
+ The request passed to section loaders must include search params:
479
+
480
+ ```typescript
481
+ const loadCmsPage = createServerFn({ method: "GET" }).handler(async (ctx) => {
482
+ const fullPath = ctx.data as string;
483
+ const [basePath] = fullPath.split("?");
484
+ const serverUrl = getRequestUrl();
485
+ const urlWithSearch = fullPath.includes("?")
486
+ ? new URL(fullPath, serverUrl.origin).toString()
487
+ : serverUrl.toString();
488
+
489
+ const request = new Request(urlWithSearch, { headers: getRequest().headers });
490
+ const page = await resolveDecoPage(basePath, matcherCtx);
491
+ const enrichedSections = await runSectionLoaders(page.resolvedSections, request);
492
+ return { ...page, resolvedSections: enrichedSections };
493
+ });
494
+ ```
495
+
496
+ ---
497
+
498
+ ## Pattern 8: Programmatic Preloading
499
+
500
+ For advanced flows (barcode scanner, autocomplete selection, keyboard navigation):
501
+
502
+ ```typescript
503
+ import { useRouter } from "@tanstack/react-router";
504
+
505
+ function BarcodeScanner() {
506
+ const router = useRouter();
507
+
508
+ const onScan = async (code: string) => {
509
+ const slug = await resolveBarcode(code);
510
+
511
+ // Preload the product page while showing feedback
512
+ await router.preloadRoute({
513
+ to: "/produto/$slug",
514
+ params: { slug },
515
+ });
516
+
517
+ // Navigate — page is already loaded, opens instantly
518
+ router.navigate({
519
+ to: "/produto/$slug",
520
+ params: { slug },
521
+ });
522
+ };
523
+ }
524
+ ```
525
+
526
+ ### Preload on Autocomplete Hover
527
+
528
+ ```typescript
529
+ function SearchSuggestion({ product }) {
530
+ const router = useRouter();
531
+ const url = relative(product.url);
532
+
533
+ return (
534
+ <Link
535
+ to={url}
536
+ onMouseEnter={() => {
537
+ router.preloadRoute({ to: url });
538
+ }}
539
+ >
540
+ {product.name}
541
+ </Link>
542
+ );
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ## Pattern 9: `<select>` with `selected` to `defaultValue`
549
+
550
+ ### Problem
551
+
552
+ ```typescript
553
+ // FRESH/Preact — works but React warns
554
+ <option value={value} selected={value === sort}>{label}</option>
555
+ ```
556
+
557
+ ### Solution
558
+
559
+ ```typescript
560
+ <select defaultValue={sort} onChange={applySort}>
561
+ {options.map(({ value, label }) => (
562
+ <option key={value} value={value}>{label}</option>
563
+ ))}
564
+ </select>
565
+ ```
566
+
567
+ ---
568
+
569
+ ## SSR + SEO Best Practices
570
+
571
+ ### Every Page is SSR by Default
572
+
573
+ TanStack Start renders on the server first. No extra config needed. But optimize:
574
+
575
+ 1. **Head metadata from loader data**:
576
+ ```typescript
577
+ export const Route = createFileRoute("/$")({
578
+ head: ({ loaderData }) => ({
579
+ meta: [
580
+ { title: loaderData?.seo?.title ?? "Espaço Smart" },
581
+ { name: "description", content: loaderData?.seo?.description ?? "" },
582
+ ],
583
+ links: loaderData?.seo?.canonical
584
+ ? [{ rel: "canonical", href: loaderData.seo.canonical }]
585
+ : [],
586
+ }),
587
+ });
588
+ ```
589
+
590
+ 2. **Structured data in sections** (JSON-LD runs server-side, no hydration needed):
591
+ ```typescript
592
+ function ProductSection({ product }) {
593
+ return (
594
+ <>
595
+ <script
596
+ type="application/ld+json"
597
+ dangerouslySetInnerHTML={{
598
+ __html: JSON.stringify({
599
+ "@context": "https://schema.org",
600
+ "@type": "Product",
601
+ name: product.name,
602
+ // ...
603
+ }),
604
+ }}
605
+ />
606
+ <div>{/* product UI */}</div>
607
+ </>
608
+ );
609
+ }
610
+ ```
611
+
612
+ 3. **Internal links as `<Link>`** — crawlers follow them AND users get SPA navigation:
613
+ ```typescript
614
+ <Link to={relative(product.url)} preload="intent">
615
+ <img src={product.image} alt={product.name} />
616
+ <span>{product.name}</span>
617
+ </Link>
618
+ ```
619
+
620
+ ---
621
+
622
+ ## Complete Migration Checklist
623
+
624
+ ### Navigation Links
625
+ - [ ] Product card `<a href>` → `<Link to preload="intent">`
626
+ - [ ] Category/NavItem `<a href>` → `<Link to preload="intent">`
627
+ - [ ] Breadcrumb `<a href>` → `<Link to>`
628
+ - [ ] Filter options `<a href>` → `<Link to>` (same-page search param change)
629
+ - [ ] Search suggestions `<a href>` → `<Link to preload="intent">`
630
+ - [ ] Footer internal links → `<Link to>`
631
+
632
+ ### Mutations
633
+ - [ ] Sort `window.location.search =` → `useNavigate`
634
+ - [ ] PriceRange `window.location.search =` → `useNavigate` with debounce
635
+ - [ ] SearchBar `<form action>` → `onSubmit` + `useNavigate`
636
+ - [ ] Newsletter form → `onSubmit` + `createServerFn`
637
+
638
+ ### Route Configuration
639
+ - [ ] `$.tsx` has `loaderDeps: ({ search }) => ({ search })`
640
+ - [ ] `$.tsx` passes search params to section loaders via Request URL
641
+ - [ ] `<select>` uses `defaultValue` instead of `<option selected>`
642
+
643
+ ### Verification
644
+
645
+ ```bash
646
+ # Internal links that are still <a> (should be <Link>):
647
+ rg '<a\s+href="/' src/components/ src/sections/ --glob '*.tsx' -l
648
+
649
+ # window.location mutations (should be useNavigate):
650
+ rg 'window\.location\.(search|href)\s*=' src/ --glob '*.{tsx,ts}'
651
+
652
+ # Forms without onSubmit (should have handler):
653
+ rg '<form[^>]*action=' src/ --glob '*.tsx' | rg -v 'onSubmit'
654
+ ```
655
+
656
+ ---
657
+
658
+ ## Quick Reference Card
659
+
660
+ | Fresh Pattern | TanStack Pattern | Benefit |
661
+ |--------------|-----------------|---------|
662
+ | `<a href={url}>` | `<Link to={url} preload="intent">` | Instant navigation |
663
+ | `window.location.search = x` | `navigate({ search })` | No reload, keeps state |
664
+ | `<form action="/s">` | `onSubmit + useNavigate` | SPA navigation |
665
+ | `<form action="/" method="POST">` | `onSubmit + createServerFn` | Server mutation |
666
+ | `<option selected>` | `<select defaultValue>` | React-compatible |
667
+ | CSS active class manually | `activeProps={{ className }}` | Automatic |
668
+ | No prefetch | `preload="intent"` | Data ready before click |
669
+ | `req.url` in loader | `loaderDeps + deps.search` | Reactive to URL changes |
670
+ | `router.push(url)` | `router.preloadRoute + navigate` | Preload then navigate |
671
+
672
+ ---
673
+
674
+ ## Related Skills
675
+
676
+ | Skill | Purpose |
677
+ |-------|---------|
678
+ | `deco-to-tanstack-migration` | Full migration playbook (imports, signals, framework) |
679
+ | `deco-islands-migration` | Eliminating the islands/ directory |
680
+ | `deco-tanstack-storefront-patterns` | Runtime patterns and fixes post-migration |
681
+ | `deco-storefront-test-checklist` | Context-aware QA checklist generation |