How server functions (invoke) work in Deco storefronts — the generate-invoke pipeline that bridges @decocms/apps pure functions to TanStack Start createServerFn with top-level declarations. Covers the root cause of CORS issues with VTEX API calls, why createServerFn must be top-level, the three-layer architecture (apps=pure functions, start=generator, site=generated invoke), the generate-invoke.ts script, and the comparison with deco-cx/deco's Proxy+HTTP invoke. Use when debugging CORS in cart/checkout, adding new server actions, understanding why invoke calls hit VTEX directly from the browser, or setting up invoke for a new site.
| Document | Topic |
|---|---|
| problem.md | The CORS problem — root cause analysis of why createServerFn inside a factory function doesn't work |
| architecture.md | Three-layer invoke architecture and comparison with deco-cx/deco |
| generator.md | The generate-invoke.ts script — how it works, how to run it, how to extend |
| troubleshooting.md | Common issues and how to debug them |
How server-side actions (cart, checkout, newsletter, masterdata) are called from the browser in Deco TanStack Start storefronts.
TanStack Start's createServerFn compiler only transforms .handler() calls at module top-level — wrapping it in a factory function (createInvokeFn) causes the compiler's "fast path" to skip it, sending raw VTEX API calls to the browser and causing CORS errors.
A build-time generator (generate-invoke.ts) reads the action definitions from @decocms/apps and emits invoke.gen.ts with each createServerFn().handler() as a top-level const, which the compiler correctly transforms into RPC stubs.
Client (useCart)
→ invoke.vtex.actions.addItemsToCart({ data: {...} })
→ createClientRpc("base64id") ← compiler-generated stub
→ POST /_server ← same domain, no CORS
→ TanStack Start server handler
→ addItemsToCart(orderFormId, items) ← pure function from @decocms/apps
→ vtexFetch → VTEX API ← server-to-server, has credentials
→ Response → client
| Layer | Package | Role |
|---|---|---|
| Commerce functions | @decocms/apps | Pure async functions (addItemsToCart, subscribe, etc.) — no framework deps |
| Generator | @decocms/start | generate-invoke.ts script that creates top-level createServerFn declarations |
| Generated bridge | Site (invoke.gen.ts) | Auto-generated file with RPC-transformable server functions |
| Consumer | Site components | Import invoke from ~/server/invoke.gen |
# 1. Generate the invoke file
npx tsx node_modules/@decocms/start/scripts/generate-invoke.ts
# 2. Import in components
import { invoke } from "~/server/invoke.gen";
const cart = await invoke.vtex.actions.addItemsToCart({
data: { orderFormId, orderItems }
});
Add to package.json:
{
"scripts": {
"generate:invoke": "tsx node_modules/@decocms/start/scripts/generate-invoke.ts",
"build": "npm run generate:blocks && npm run generate:invoke && npm run generate:schema && tsr generate && vite build"
}
}
| File | Location | Purpose |
|---|---|---|
generate-invoke.ts | @decocms/start/scripts/ | Build-time generator script |
invoke.gen.ts | Site src/server/ | Generated file with top-level server functions |
vtex/invoke.ts | @decocms/apps/ | Source of truth for action definitions (parsed by generator) |
vtex/actions/*.ts | @decocms/apps/ | Pure commerce functions |
Re-run npm run generate:invoke when:
@decocms/apps/vtex/invoke.ts@decocms/apps dependency