docs: add reusable Deco skills documentation - #1215
Conversation
Add five battle-tested skills for building fast, maintainable storefronts on TanStack Start with React and VTEX: - deco-minicart-configuravel: API-frugal on-demand minicart with CMS config - deco-add-to-cart-slim: Slim add-to-cart response (~0.3KB vs 97KB) - deco-signal-reactivity-react: Signal reactivity gotcha in Preact→React migration - deco-micro-skeletons: Fine-grained loading states without layout shift - deco-nav-prefetch: Hybrid HTML + SPA prefetch strategy All reference implementations tested at montecarlo-tanstack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tagging OptionsShould a new tag be published when this PR is merged?
|
📝 WalkthroughWalkthroughAdds a ChangesSkills documentation
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@skills/deco-add-to-cart-slim.md`:
- Line 13: Correct the terminology at skills/deco-add-to-cart-slim.md lines 13,
75-76, and 84-85, and skills/README.md lines 12-15: describe the optimization as
reducing the add-to-cart response payload, qualify the 97 KB to 0.3 KB figure as
a response-size comparison, and distinguish response-size verification from
request-size measurement; mirror this terminology in the catalog.
- Around line 19-29: Update the _addItemsToCartSlim example in
skills/deco-add-to-cart-slim.md to import createServerFn from its established
package so the snippet is copy-paste runnable, or explicitly mark the snippet as
abbreviated if the import is intentionally omitted.
- Around line 30-33: Replace the pass-through validator in the add-to-cart
endpoint with server-side schema validation for orderFormId, each order item’s
id and seller, and a strictly positive quantity. Keep the validated payload
shape aligned with addItemsToCart, or move the same contract into addItemsToCart
and document the boundary.
In `@skills/deco-micro-skeletons.md`:
- Around line 58-69: Update the price block containing the sale and list prices
so it always reserves space for two lines when sale equals list or no discount
exists. Keep the discount text visually hidden or apply an equivalent matching
min-height, while preserving the existing visible strikethrough behavior when
sale differs from list and the pending animation.
- Around line 81-85: Update the loading-state documentation and verification
text to resolve the contradiction around disabled controls: describe the widget
as remaining visible but disabled, and explicitly document whether input
received while pending is rejected or queued. Apply the same wording
consistently in the Benefits section and the corresponding lines around the
example.
In `@skills/deco-minicart-configuravel.md`:
- Around line 106-111: Update the runtime-default mitigation in the CMS Codegen
Migration Blocker section to use the documented MinicartConfig key
showAddToCartToast instead of autoOpenOnAdd. Keep the intended default behavior
of enabling the add-to-cart toast, unless you explicitly document autoOpenOnAdd
and its mapping to showAddToCartToast.
In `@skills/deco-nav-prefetch.md`:
- Around line 68-76: Update the Link component’s hover prefetch flow so hovering
triggers actual intent preloading: configure the router with defaultPreload:
"intent" or pass preload="intent" to the rendered link. Remove the no-op timeout
callback and preserve the existing prefetch opt-out behavior.
- Line 130: Update the prefetch behavior documented in the Mobile guidance so
touch devices are gated in JavaScript rather than relying on the CSS `@media
(hover: hover)` query. Use `matchMedia` or pointer detection, or configure
`preload={false}` for touch devices, ensuring intent-based preloading and
touch-start handlers remain disabled.
- Around line 31-32: Update the script-loading logic around script.src to use a
valid, pinned package URL instead of the nonexistent bagaggio@latest endpoint,
and ensure the script is not appended again when the component or setup flow
remounts.
- Around line 37-39: Update the Joias, Anéis, and Pulseiras anchor elements to
remove rel="prefetch", keeping them as plain links so instant.page/bagaggio
handles hover prefetch; alternatively, replace this approach with explicit link
prefetch elements for these destinations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 943fff45-3f32-4fc3-8ed0-733fabcf85f6
📒 Files selected for processing (6)
skills/README.mdskills/deco-add-to-cart-slim.mdskills/deco-micro-skeletons.mdskills/deco-minicart-configuravel.mdskills/deco-nav-prefetch.mdskills/deco-signal-reactivity-react.md
| Traditional add-to-cart returns the full VTEX OrderForm (~97 KB) on every add, even though the browser only needs: `orderFormId`, item count, and total. This causes bandwidth waste and network latency spikes. | ||
|
|
||
| ## Solution | ||
| Create a slim server function that runs the add server-side but returns only `{ orderFormId, itemCount, totalQuantity, value }` (~0.3 KB). The full OrderForm is fetched on-demand only when the drawer opens. |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Correct the bandwidth terminology across the guide and catalog.
The implementation trims the add-to-cart response by omitting the full OrderForm; it does not make the request itself 97 KB → 0.3 KB. Update both documents to distinguish response payload size from total network traffic.
skills/deco-add-to-cart-slim.md#L13-L13: describe the returned response as slim rather than claiming general bandwidth reduction.skills/deco-add-to-cart-slim.md#L75-L76: qualify the 97 KB → 0.3 KB metric as a response-payload comparison.skills/deco-add-to-cart-slim.md#L84-L85: verify response size separately from request size.skills/README.md#L12-L15: mirror the corrected terminology in the catalog.
📍 Affects 2 files
skills/deco-add-to-cart-slim.md#L13-L13(this comment)skills/deco-add-to-cart-slim.md#L75-L76skills/deco-add-to-cart-slim.md#L84-L85skills/README.md#L12-L15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-add-to-cart-slim.md` at line 13, Correct the terminology at
skills/deco-add-to-cart-slim.md lines 13, 75-76, and 84-85, and skills/README.md
lines 12-15: describe the optimization as reducing the add-to-cart response
payload, qualify the 97 KB to 0.3 KB figure as a response-size comparison, and
distinguish response-size verification from request-size measurement; mirror
this terminology in the catalog.
| // src/server/invoke.ts | ||
| import { addItemsToCart } from "@decocms/apps-vtex/actions/checkout"; | ||
|
|
||
| export interface SlimCartResult { | ||
| orderFormId: string; | ||
| itemCount: number; | ||
| totalQuantity: number; | ||
| value: number; | ||
| } | ||
|
|
||
| const _addItemsToCartSlim = createServerFn({ method: "POST" }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the target file and inspect surrounding lines.
git ls-files 'skills/deco-add-to-cart-slim.md'
wc -l skills/deco-add-to-cart-slim.md
cat -n skills/deco-add-to-cart-slim.md | sed -n '1,140p'
# Search for the referenced API usage/imports in the repo docs.
rg -n "createServerFn|addItemsToCartSlim|SlimCartResult|inputValidator|orderFormId" skills . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'Repository: deco-cx/deco
Length of output: 7244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect nearby documentation files that might define the snippet style.
fd -a -t f 'deco-add-to-cart-slim.md|deco-add-to-cart' . || trueRepository: deco-cx/deco
Length of output: 201
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect other skills docs for snippet conventions around imports/abbreviations.
for f in skills/*.md; do
printf '\n== %s ==\n' "$f"
rg -n "omitted|abbreviated|import .*createServerFn|// src/server/invoke.ts|```" "$f" || true
done | sed -n '1,260p'Repository: deco-cx/deco
Length of output: 210
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for explicit guidance or examples showing whether imports are omitted in code snippets.
rg -n "omitted imports|abbreviated|copy-paste|snippet|```ts|```js|```tsx|```jsx" skills/*.md | sed -n '1,220p'
# Sample a few nearby skills docs for code-block style around imports.
for f in skills/*.md; do
echo "== $f =="
sed -n '1,80p' "$f" | rg -n "^import |^// |^```|^# |^## " || true
done | sed -n '1,260p'Repository: deco-cx/deco
Length of output: 453
Import createServerFn or mark the snippet as abbreviated.
As written, the server-function example references createServerFn without showing its import, so it isn’t copy-paste runnable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-add-to-cart-slim.md` around lines 19 - 29, Update the
_addItemsToCartSlim example in skills/deco-add-to-cart-slim.md to import
createServerFn from its established package so the snippet is copy-paste
runnable, or explicitly mark the snippet as abbreviated if the import is
intentionally omitted.
| .inputValidator((data: { | ||
| orderFormId: string; | ||
| orderItems: Array<{ id: string; seller: string; quantity: number }>; | ||
| }) => data) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the referenced skill and any related action/docs.
git ls-files | rg '^skills/deco-add-to-cart-slim\.md$|deco-add-to-cart|add-to-cart'
# Show the referenced section with line numbers.
if [ -f skills/deco-add-to-cart-slim.md ]; then
echo "---- skills/deco-add-to-cart-slim.md ----"
cat -n skills/deco-add-to-cart-slim.md | sed -n '1,120p'
fi
# Search for the validator and any downstream payload validation/contract docs.
rg -n --hidden --glob '!**/.git/**' \
'inputValidator\(\(data: \{|\borderFormId\b|\borderItems\b|seller: string|quantity: number|deco-add-to-cart-slim' \
skills . 2>/dev/null || trueRepository: deco-cx/deco
Length of output: 7413
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the add-to-cart action implementation or type definitions.
rg -n --hidden --glob '!**/.git/**' \
'addItemsToCart|actions/checkout|orderItems: Array<\{ id: string; seller: string; quantity: number \}>' \
. 2>/dev/null || true
# If there is a likely action file, show the relevant sections with line numbers.
for f in $(git ls-files | rg '(^|/)(checkout|actions|server|sdk).*\.(ts|tsx|js|md)$' || true); do
if rg -q 'addItemsToCart|orderItems|seller|quantity' "$f"; then
echo "---- $f ----"
cat -n "$f" | sed -n '1,220p'
echo
fi
doneRepository: deco-cx/deco
Length of output: 1458
🌐 Web query:
TanStack Start createServerFn inputValidator runtime validation identity function semantics
💡 Result:
In TanStack Start, the inputValidator method (often used interchangeably with validator) is used to perform runtime validation and data transformation on the input passed to a createServerFn before it reaches the handler [1][2][3]. Core semantics and runtime behavior: 1. Runtime Validation: The inputValidator accepts a validation schema (such as Zod, Valibot, or ArkType via Standard Schema) or a custom function [1][2][3]. If validation fails, it throws an error, preventing the handler from executing [4][2][3]. 2. Identity Function Semantics: When a raw function is passed as an inputValidator, it acts as an identity or transformation function [2][3]. It receives the input unknown, and the returned value is provided to the handler as the validated data [1][2]. If the function does not explicitly validate the input (e.g., throwing on invalid types), it essentially treats the input as trusted, which is generally discouraged for security [2]. 3. Supported Validator Shapes: The underlying execution logic (execValidator) supports multiple patterns [3]: - Standard Schema: Objects containing a ~standard property (e.g., Zod, Valibot) [3]. - Parseable Objects: Objects with a parse method (e.g., older Zod versions) [3]. - Functions: A simple callback function, which provides the identity/transformation semantics mentioned above [3]. 4. Error Handling: Current implementations of inputValidator generally throw a generic Error when validation fails [3]. Note that in some versions, this can cause structured validation errors (like those from Zod) to lose their metadata during serialization across the network boundary, as they are often stringified into the error message [4]. For developers, it is recommended to use Standard Schema-compatible libraries (like Zod) to ensure both type-safe inference and robust runtime validation [1][2]. While custom identity functions are supported, they do not provide automatic type narrowing or validation unless manually implemented within the function body [2].
Citations:
- 1: https://tanstack-router-54.mintlify.app/start/api/server-functions
- 2: https://tanstack-router-63.mintlify.app/start/concepts/server-functions
- 3: https://github.com/TanStack/router/blob/a4154714/packages/start-client-core/src/createServerFn.ts
- 4: inputValidator validation errors lose structure during serialization - issues array becomes JSON string in error.message TanStack/router#6428
Validate the add-to-cart payload at the server boundary. inputValidator((data) => data) is a pass-through, so this endpoint still accepts arbitrary client input. Add schema checks for orderFormId, id, seller, and quantity > 0, or move that contract into addItemsToCart and document it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-add-to-cart-slim.md` around lines 30 - 33, Replace the
pass-through validator in the add-to-cart endpoint with server-side schema
validation for orderFormId, each order item’s id and seller, and a strictly
positive quantity. Keep the validated payload shape aligned with addItemsToCart,
or move the same contract into addItemsToCart and document the boundary.
| <div className={`flex flex-col justify-end items-end ${isPending ? 'animate-pulse opacity-40' : ''}`}> | ||
| {sale != list && ( | ||
| <span className="text-[#AAA89C] text-xs line-through"> | ||
| {formatPrice(list, currency, locale)} | ||
| </span> | ||
| )} | ||
| <span className="text-base font-semibold"> | ||
| {formatPrice(sale, currency, locale)} | ||
| </span> | ||
| </div> | ||
| ``` | ||
| The block stays in the DOM with its 2 lines intact. When pending, it pulses (opacity drop + animation). Exact dimensions are preserved. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reserve the discount row when no sale price exists.
{sale != list && (...)} still changes the block from two lines to one when the discount disappears, so this example does not guarantee zero layout shift. Reserve a fixed two-line slot with an invisible placeholder or matching min-height.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-micro-skeletons.md` around lines 58 - 69, Update the price block
containing the sale and list prices so it always reserves space for two lines
when sale equals list or no discount exists. Keep the discount text visually
hidden or apply an equivalent matching min-height, while preserving the existing
visible strikethrough behavior when sale differs from list and the pending
animation.
| ## Benefits | ||
| - **Zero layout shift:** No CLS violation, no row collapse. | ||
| - **Visual feedback:** User still sees loading state (pulse + opacity). | ||
| - **No interaction layer:** No need to disable the real widget — it's just dimmed. | ||
| - **Semantic:** The actual content stays in the DOM; CSS handles the visual feedback. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Resolve the disabled/clickable contradiction.
The example sets disabled={...}, but the benefits say the widget need not be disabled and verification says it stays clickable. A disabled control is not clickable; describe this as “stays visible but disabled,” and document whether pending input is rejected or queued.
Also applies to: 96-99
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-micro-skeletons.md` around lines 81 - 85, Update the
loading-state documentation and verification text to resolve the contradiction
around disabled controls: describe the widget as remaining visible but disabled,
and explicitly document whether input received while pending is rejected or
queued. Apply the same wording consistently in the Benefits section and the
corresponding lines around the example.
| ### 5. CMS Codegen Migration Blocker (7.20+ Bump) | ||
| After bumping to `@decocms/*@7.20+`, the generators (`@decocms/blocks-cli`) write to `.deco/` in a NEW format, but a repo migrated earlier still consumes OLD-format files in `src/server/{cms,admin}/` (from `@decocms/start`). Running the generators does NOT update what the app reads. | ||
|
|
||
| **Consequence:** New CMS props (`cart.config`, toast toggle) are code-ready but NOT admin-editable until the repo does the codegen migration (switch `setup.ts` importers to `.deco/` OR regenerate all artifacts consistently). | ||
|
|
||
| **Mitigation:** Design runtime defaults so the site behaves correctly WITHOUT any CMS config (e.g. `autoOpenOnAdd: false` → toast active, `freeShippingTarget: 500`). Then editability lands for free once the migration runs. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use the documented configuration key for the runtime default.
MinicartConfig documents showAddToCartToast, but the migration mitigation introduces autoOpenOnAdd. This is a contract mismatch: implementers may configure a key that the loader and header never read.
Proposed correction
-Then editability lands for free once the migration runs.
+Then editability lands for free once the migration runs.Use the existing documented field in the preceding example, e.g. showAddToCartToast: true, or explicitly document a separate autoOpenOnAdd field and its mapping.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-minicart-configuravel.md` around lines 106 - 111, Update the
runtime-default mitigation in the CMS Codegen Migration Blocker section to use
the documented MinicartConfig key showAddToCartToast instead of autoOpenOnAdd.
Keep the intended default behavior of enabling the add-to-cart toast, unless you
explicitly document autoOpenOnAdd and its mapping to showAddToCartToast.
| script.src = "https://cdn.jsdelivr.net/npm/bagaggio@latest"; | ||
| document.head.appendChild(script); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://registry.npmjs.org/bagaggio | jq -r '.["dist-tags"].latest, .versions | keys[]' | tailRepository: deco-cx/deco
Length of output: 201
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '--- file outline ---'
wc -l skills/deco-nav-prefetch.md
sed -n '1,120p' skills/deco-nav-prefetch.md
echo
echo '--- package lookup candidates ---'
python3 - <<'PY'
import urllib.request, urllib.error, json, re
for name in ["bagaggio", "bagagggio", "bagage", "baguette"]:
url = f"https://registry.npmjs.org/{name}"
try:
with urllib.request.urlopen(url, timeout=20) as r:
data = json.load(r)
print(name, "OK", data.get("dist-tags", {}).get("latest"))
except Exception as e:
print(name, "ERR", type(e).__name__, e)
PYRepository: deco-cx/deco
Length of output: 4377
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '--- surrounding lines with numbering ---'
cat -n skills/deco-nav-prefetch.md | sed -n '1,120p'Repository: deco-cx/deco
Length of output: 4547
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
git ls-files | rg '^skills/deco-nav-prefetch\.md$' || trueRepository: deco-cx/deco
Length of output: 178
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '--- npm metadata ---'
npm view bagaggio version dist-tags --registry=https://registry.npmjs.org/ --json 2>&1 || true
echo
echo '--- registry HTTP status ---'
python3 - <<'PY'
import urllib.request, urllib.error, ssl
url = "https://registry.npmjs.org/bagaggio"
ctx = ssl.create_default_context()
try:
with urllib.request.urlopen(url, context=ctx, timeout=20) as r:
print("status", r.status)
print("content-type", r.headers.get("content-type"))
print(r.read(200).decode("utf-8", "replace"))
except Exception as e:
print(type(e).__name__, e)
PYRepository: deco-cx/deco
Length of output: 1306
Use a valid, pinned script URL here. bagaggio isn’t published on npm, so https://cdn.jsdelivr.net/npm/bagaggio@latest won’t load. If this stays external, point it at a real package/version and avoid re-appending the script on remount.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-nav-prefetch.md` around lines 31 - 32, Update the script-loading
logic around script.src to use a valid, pinned package URL instead of the
nonexistent bagaggio@latest endpoint, and ensure the script is not appended
again when the component or setup flow remounts.
| <a href="/joias" rel="prefetch">Joias</a> | ||
| <a href="/aneis" rel="prefetch">Anéis</a> | ||
| <a href="/pulseiras" rel="prefetch">Pulseiras</a> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel \
| rg -n 'prefetch|<a>|<link>'Repository: deco-cx/deco
Length of output: 30870
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Map the file and inspect the relevant section with line numbers.
wc -l skills/deco-nav-prefetch.md
sed -n '1,120p' skills/deco-nav-prefetch.md | cat -n
# Look for other prefetch-related guidance in the repo.
rg -n --hidden --glob '!**/.git/**' 'rel="prefetch"|prefetch|speculationrules|hover' skills . 2>/dev/null | head -n 200Repository: deco-cx/deco
Length of output: 12612
🌐 Web query:
MDN a rel prefetch support anchor element instant.page auto-detect rel="prefetch"
💡 Result:
The rel="prefetch" attribute is not supported on anchor () elements [1][2]. According to MDN and relevant web standards, rel="prefetch" is only intended for use on elements or via the Link HTTP header to inform the browser of resources likely to be needed for future navigations [3][1][4]. Placing rel="prefetch" directly on an tag will not cause the browser to automatically prefetch the linked document [1][2]. The library instant.page implements a workaround to achieve this behavior [5][6]. Instead of relying on the browser to natively detect rel="prefetch" on anchor elements, instant.page uses JavaScript to detect user interactions—such as hovering over or touching a link [7]. When an interaction occurs, the library dynamically creates or updates a element in the document head [8][6]. This programmatically triggers the browser's native prefetch mechanism for that specific URL, effectively enabling "just-in-time" preloading for anchor elements [5][7].
Citations:
- 1: https://devdoc.net/web/developer.mozilla.org/en-US/docs/Link_prefetching_FAQ.html
- 2: https://stackoverflow.com/questions/34105233/html-prefetch-resources-in-anchor-tags
- 3: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/prefetch
- 4: https://web.dev/articles/link-prefetch
- 5: https://instant.page/tech
- 6: https://www.ericshefferman.com/2019/02/17/https-instant-page-1-1-0/
- 7: https://instant.page/
- 8: https://urlscan.io/responses/bee6c0bc3e2509b75a0a4bbc930cc89d9dc3f7829e6024447e10293ba004de4a/
🌐 Web query:
instant.page rel="prefetch" anchor hover documentation
💡 Result:
instant.page is a lightweight JavaScript library that improves page load speeds by preloading linked pages just before a user clicks on them [1][2]. It functions primarily by dynamically appending a tag to the document head when specific user interactions occur [2]. Core Functionality By default, instant.page initiates preloading based on the following interactions: - Desktop: Preloads when a user hovers over a link for 65 milliseconds [1][3]. - Mobile: Preloads when a user starts touching their display (touchstart) [1][3]. The library is designed to be highly efficient, preloading only the HTML of the target page to minimize impact on bandwidth and CPU [1]. Configuration and Intensity You can customize the preloading behavior using the data-instant-intensity attribute on the or <script> tag [3][4]: - Mousedown: To trigger preloading when the user starts pressing the mouse button (before release), use data-instant-intensity="mousedown" [3][5]. To apply this only on desktop, use data-instant-intensity="mousedown-only" [3]. - Viewport: To preload links as soon as they become visible in the viewport, use data-instant-intensity="viewport" (on mobile) or data-instant-intensity="viewport-all" (on all devices) [4]. - Custom Hover Delay: To change the hover delay, pass the number of milliseconds as the attribute value (e.g., data-instant-intensity="150" for a 150 ms delay) [3][5]. Implementation To use instant.page, add the following script tag before the closing tag of your website: <script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYnSU0NYmgav2LauIAEMoxNYxS2o2oYQO22S3V0451Oq86414p6QJm7"></script> The use of type="module" ensures that the script only loads in modern browsers that support the prefetching mechanisms the library utilizes [2].
Citations:
- 1: https://instant.page/
- 2: https://css-tricks.com/instant-page/
- 3: https://instant.page/intensity
- 4: https://instant.page/v3
- 5: https://instant.page/2
Replace rel="prefetch" on these anchors skills/deco-nav-prefetch.md:37-39 — <a> doesn’t use rel="prefetch" for browser prefetching. Keep these links plain and let instant.page/bagaggio handle hover prefetch, or emit explicit <link rel="prefetch" href="..."> elements instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-nav-prefetch.md` around lines 37 - 39, Update the Joias, Anéis,
and Pulseiras anchor elements to remove rel="prefetch", keeping them as plain
links so instant.page/bagaggio handles hover prefetch; alternatively, replace
this approach with explicit link prefetch elements for these destinations.
| export function Link({ to, prefetch = true, children }: Props) { | ||
| const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| const handleMouseEnter = () => { | ||
| if (prefetch) { | ||
| hoverTimeoutRef.current = setTimeout(() => { | ||
| // Trigger SPA prefetch: TanStack Start Link handles this internally | ||
| // or use router.preloadRoute(to) if available | ||
| }, 100); // small delay to avoid prefetch on hover-throughs |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://tanstack.com/router/v1/docs/guide/preloading \
| rg -n 'defaultPreload|preload prop|preloadRoute'Repository: deco-cx/deco
Length of output: 50369
🏁 Script executed:
set -euo pipefail
printf '--- file excerpt ---\n'
cat -n skills/deco-nav-prefetch.md | sed -n '1,140p'
printf '\n--- repository search ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' \
'defaultPreload|preload="intent"|preloadRoute|router\.preloadRoute|prefetch' \
skills .Repository: deco-cx/deco
Length of output: 241
🏁 Script executed:
set -euo pipefail
cat -n skills/deco-nav-prefetch.md | sed -n '60,95p'Repository: deco-cx/deco
Length of output: 1325
Wire this link to intent preloading
The hover timeout never triggers any preload work, so this wrapper still does nothing on hover. Use defaultPreload: "intent" on the router or pass preload="intent" here; otherwise the card hover behavior stays a no-op.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-nav-prefetch.md` around lines 68 - 76, Update the Link
component’s hover prefetch flow so hovering triggers actual intent preloading:
configure the router with defaultPreload: "intent" or pass preload="intent" to
the rendered link. Remove the no-op timeout callback and preserve the existing
prefetch opt-out behavior.
| - **Network tab:** Hover nav item → prefetch request appears (status 200, cached or from network). | ||
| - **Product card:** Hover a few cards → SPA prefetch requests appear. | ||
| - **Page transition:** Click after hover → page loads instantly (or very fast) from cache. | ||
| - **Mobile:** Disable prefetch on touch devices (use `@media (hover: hover)`) to save bandwidth. |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline skills/deco-nav-prefetch.md --view expanded || true
printf '\n== Relevant lines around 130 ==\n'
sed -n '110,150p' skills/deco-nav-prefetch.md | cat -n
printf '\n== Search for prefetch/preload references in repo ==\n'
rg -n "preload={false}|prefetch|preload|hover: hover|matchMedia|touch-start|intent preloading|deco-nav-prefetch" .Repository: deco-cx/deco
Length of output: 1961
🌐 Web query:
TanStack Router preloading touch-start intent hover media query preload={false} docs
💡 Result:
In TanStack Router, preloading is a mechanism to load route dependencies before a user navigates to them [1][2]. When using the "intent" preloading strategy, the router monitors specific user interactions on Link components to trigger preloading [1][3]. Intent-based Preloading The "intent" strategy triggers preloading when a user hovers over a Link component or when a touchstart event is detected [1][4][5]. By default, this preloading occurs after a 50ms delay [2][6][5]. This delay can be configured globally via the router's defaultPreloadDelay option or locally on individual Link components using the preloadDelay prop [1][3][5]. Configuring Preload={false} To disable preloading for specific routes or globally, you can set the preload option to false [4][7]. Global configuration: You can set the default behavior for your entire router by configuring the defaultPreload option in your router instantiation [4][7]. If set to false, routes will not be preloaded by default [4]. const router = createRouter({ routeTree, defaultPreload: false, // Disables automatic preloading globally }) Per-route configuration: You can override global settings for specific routes by setting the preload property in the route configuration [7]. export const Route = createFileRoute('/posts/$postId')({ preload: false, // Disables preloading for this specific route loader: async ({ params }) => {... }, }) It is important to note that preloading is distinct from media queries; TanStack Router's intent-based system relies on browser events (hover, touchstart) rather than CSS media queries to determine when to trigger the loading process [1][3][8]. The framework ensures that preloading behavior—specifically for the "intent" mode—is debounced and triggered only when the explicit configuration is enabled [8].
Citations:
- 1: https://tanstack.com/router/latest/docs/guide/preloading
- 2: https://github.com/TanStack/router/blob/main/docs/router/guide/preloading.md
- 3: https://tanstack.com/router/v1/docs/guide/navigation
- 4: https://tanstack.com/router/latest/docs/api/router/RouterOptionsType
- 5: https://github.com/TanStack/router/blob/main/packages/router-core/skills/router-core/navigation/SKILL.md
- 6: https://tanstack.com/router/v1/docs/guide/preloading.md
- 7: https://tanstack-router-63.mintlify.app/router/concepts/prefetching
- 8: fix(react-router,solid-router,vue-router): correct preload triggers for "intent" option TanStack/router#6747
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('skills/deco-nav-prefetch.md')
print(p.exists(), p.stat().st_size if p.exists() else None)
PYRepository: deco-cx/deco
Length of output: 160
Gate prefetch in JavaScript, not CSS. @media (hover: hover) only affects styles; it won’t stop intent-based preloading or touch-start handlers. Use matchMedia/pointer detection, or set preload={false} on touch devices.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/deco-nav-prefetch.md` at line 130, Update the prefetch behavior
documented in the Mobile guidance so touch devices are gated in JavaScript
rather than relying on the CSS `@media (hover: hover)` query. Use `matchMedia`
or pointer detection, or configure `preload={false}` for touch devices, ensuring
intent-based preloading and touch-start handlers remain disabled.
Add five battle-tested skills for building fast, maintainable storefronts on TanStack Start with React and VTEX.
Skills Added
deco-minicart-configuravel — API-frugal on-demand minicart with CMS config, React Query, canonical Minicart shape, micro-skeletons, and toast-vs-drawer toggle. Zero getOrCreateCart on page load.
deco-add-to-cart-slim — Slim add-to-cart response payload (0.3 KB vs 97 KB). Returns only orderFormId, itemCount, totalQuantity, value; full cart hydration deferred to drawer-open intent.
deco-signal-reactivity-react — Critical Preact→React migration gotcha: reading signal.value in render doesn't re-render without explicit subscription via useSignalValue hook. Symptom: drawer/modal "does not open" on click despite handler firing.
deco-micro-skeletons — Fine-grained loading states per line/section without layout shift. Use pulse-in-place (not fixed-size boxes) to preserve exact dimensions and avoid CLS violations.
deco-nav-prefetch — Hybrid HTML + SPA prefetch strategy: HTML prefetch-on-hover for nav links (bagaggio/instant.page), SPA Link wrapper prefetch for product cards (TanStack Start).
All skills reference tested implementations in montecarlo-tanstack (TanStack Start + React + VTEX).
🤖 Generated with Claude Code
Summary by cubic
Adds five reusable “Deco skills” docs with tested patterns to build faster, API-frugal storefronts on TanStack Start with React and VTEX. These docs cover minicart architecture, slim add-to-cart, micro-skeletons, signal reactivity, and navigation prefetch.
deco-minicart-configuravel: On-demand VTEX minicart with CMS config, React Query, canonical Minicart, micro-skeletons, and toast vs drawer. NogetOrCreateCarton load.deco-add-to-cart-slim: Slim add-to-cart payload (~0.3 KB vs ~97 KB). Full cart hydration only on drawer open.deco-signal-reactivity-react: Preact→React signals gotcha. UseuseSignalValueto subscribe in render.deco-micro-skeletons: Pulse-in-place loading states that avoid CLS and keep layout stable.deco-nav-prefetch: Hybrid prefetch (HTML hover for nav + SPA link prefetch for cards).All skills reference a working implementation in
montecarlo-tanstack.Written for commit 627e035. Summary will update on new commits.
Summary by CodeRabbit