A deco.cx storefront built on TanStack Start + React 19 + Cloudflare Workers, with Shopify as the commerce backend.
This is a site repo — it consumes the @decocms/start framework (CMS bridge, admin protocol, worker entry, edge caching) and @decocms/apps (commerce loaders/actions). UI, sections, and routes live here.
| Layer | Tech |
|---|---|
| Runtime | Cloudflare Workers (nodejs_compat) |
| Framework | TanStack Start / TanStack Router |
| UI | React 19 + React Compiler |
| Styles | Tailwind CSS v4 + DaisyUI |
| Build | Vite 7 |
| Data | TanStack Query + TanStack Store, server functions |
| Commerce | Shopify Storefront API (via @decocms/apps/shopify) |
| CMS | Deco admin protocol (via @decocms/start) |
| Deploy | Wrangler (Cloudflare Workers) |
If you have an existing Deco storefront on the legacy Fresh + Preact + Deno stack, the @decocms/start framework ships a migration CLI that takes you from a Fresh site to a working TanStack Start + Cloudflare Workers site in one pass — this repo is one of the outputs (see MIGRATION_REPORT.md).
The script does six phases automatically:
- Analyze — scan source, detect Preact/Fresh/Deco patterns, GTM, commerce platform
- Scaffold — generate
package.json,tsconfig.json,vite.config.ts,wrangler.jsonc,src/server.ts,src/worker-entry.ts,src/router.tsx,src/setup.ts,src/cache-config.ts,src/routes/{__root,$,index,deco/*},src/styles/app.css, image components,useCart/useUser/useWishlisthooks, types - Transform — rewrite imports (70+ rules), JSX attrs, Fresh APIs, Deno-isms, Tailwind v3 → v4
- Cleanup — delete
islands/, old routes,deno.json; movestatic/→public/ - Report — write
MIGRATION_REPORT.mdwith manual review items - Verify — 18+ smoke tests (zero old imports, scaffolded files exist)
Your existing src/sections/, src/components/, and .deco/blocks/ carry over unchanged. The script gets you to "builds clean with zero old imports" — manual work starts at platform hooks (useCart) and runtime tuning, tracked in MIGRATION_NEXT_STEPS.md.
@decocms/start ships an Agent Skill that primes Claude Code, Cursor, Codex, and other AI coding tools with the full 12-phase migration playbook plus the reference docs and templates. The skill knows what @decocms/start supports, runs the migration script for you, and flags anything that needs manual attention as it goes — so you stay in a conversation rather than juggling flags.
Install the skill once:
npx skills add decocms/deco-startOpen your Fresh site in your AI tool and prompt:
migrate this project to TanStack Start
The agent will analyze the site, run the migration phases, talk through anything ambiguous (CSP domains, site-specific loaders, GTM IDs, custom Fresh handlers), and stop on the manual-review items so you can answer in plain English instead of editing config by hand.
See @decocms/start#migrating-from-freshpreactdeno for the full skill index.
If you'd rather drive it yourself, the same logic is exposed as a CLI. From the root of your existing Fresh site, with nothing pre-installed:
# Preview changes (no files written):
npx -p @decocms/start deco-migrate --dry-run --verbose
# Run the migration in place:
npx -p @decocms/start deco-migrate
# Migrate a different directory:
npx -p @decocms/start deco-migrate --source ./my-siteFlags:
| Flag | Description |
|---|---|
--source <dir> |
Source directory (default: current directory) |
--dry-run |
Preview changes without writing files |
--verbose |
Show detailed output for every file |
--help, -h |
Show help |
After it finishes:
npm install
npm run generate:blocks
npm run generate:schema
npx tsr generate
npm run devThen open MIGRATION_REPORT.md for the manual review checklist (CSP domains, site-specific loaders, GTM, etc.).
Open MIGRATION_NEXT_STEPS.md in this repo for the canonical follow-up checklist used during the migration of this site. The recurring patterns are:
- Replace
window.STOREFRONT.*channels (USER, WISHLIST) withsrc/platform/<domain>/modules following thecart/shape:*.types.ts,*.actions.ts(createServerFn),*.hooks.ts(useQuery+useMutation),*.<commerce>.tsadapter,index.tsbarrel. - Switch internal navigation from
<a href>to<Link preload="intent">from@tanstack/react-router. - Group flat section
Propsinto*Configsub-interfaces with JSDoc (seesrc/sections/Product/ProductDetails.tsx). - Add scoped skeletons via
useRouterState({ select: s => s.isLoading })for the bits that actually change during a route transition. - Decompose god-components into narrow leaves so React Compiler can auto-memoize them.
Requires Node 20+ and npm.
npm install
npm run devOpen http://localhost:5173.
| Script | What it does |
|---|---|
npm run dev |
Start Vite dev server |
npm run dev:clean |
Wipe Vite/Wrangler/TanStack caches and start fresh |
npm run build |
Generate blocks/schema/sections/loaders/routes, then vite build |
npm run preview |
Preview the production build locally |
npm run deploy |
npm run build then wrangler deploy |
npm run typecheck |
tsc --noEmit |
npm run format / format:check |
Prettier on src/**/*.{ts,tsx} |
npm run knip / knip:fix |
Find / auto-fix unused exports and files |
npm run tailwind:lint / tailwind:fix |
Lint/auto-fix Tailwind class usage |
npm run generate:* |
Re-run a single codegen step (blocks, schema, sections, loaders, routes, invoke) |
src/
├── apps/ # Site app composition (apps/site.ts)
├── routes/ # TanStack Router file routes (__root, $, index, deco/*, account, login)
├── sections/ # CMS-rendered sections (Header, Footer, Product, Newsletter, …)
├── components/ # UI components (header, minicart, product, search, ui, …)
├── platform/ # Domain state — TanStack Query hooks + createServerFn actions
│ ├── cart/ # cart.{types,actions,hooks,shopify}.ts
│ ├── user/
│ └── wishlist/
├── loaders/ # Site-local CMS loaders (user, wishlist)
├── actions/ # Site-local invoke handlers (wishlist/submit, shipping/simulate)
├── hooks/ # useCart, useUser, useWishlist
├── sdk/ # signal, clx, debounce, deviceServer, logger
├── styles/app.css # Tailwind v4 entry
├── setup.ts # Wires framework + apps + sections (called from worker entry)
├── setup/ # Section-specific prop enrichment
├── cache-config.ts # Edge cache profile overrides
├── server.ts # TanStack Start server entry
├── worker-entry.ts # Cloudflare Worker entry: admin protocol, CSP, segmentation, caching
├── router.tsx # Router configuration
├── runtime.ts # Runtime helpers
├── context.ts # Site context
└── server/cms/ # Generated: blocks.gen.ts, sections.gen.ts (do not edit by hand)
- A request hits
src/worker-entry.ts→createDecoWorkerEntry(admin routes, edge cache, CSP, device segmentation). - Non-admin requests fall through to the TanStack Start server entry (
src/server.ts). - The catch-all route (
src/routes/$.tsx) calls the framework's CMS resolver, which loads the page's blocks viasrc/server/cms/blocks.gen.ts. - Blocks resolve to sections under
src/sections/. Sections receive props enriched by their loader and metadata fromapplySectionConventionsinsetup.ts. - Commerce data (Shopify PDP, PLP, search, cart) comes from
@decocms/apps/shopifyloaders, wired viaautoconfigApps.
Domain state (cart, user, wishlist) follows a single pattern under src/platform/<domain>/:
<domain>.types.ts # platform-agnostic state shape
<domain>.actions.ts # createServerFn wrappers (run on the worker)
<domain>.hooks.ts # useQuery + useMutation
<domain>.shopify.ts # adapter: Shopify response → state shape
index.ts # barrel
src/routes/__root.tsx prefetches cart + user in beforeLoad and seeds the QueryClient. The minicart drawer is mounted at root and driven by the useCart() hook.
For navigation, use <Link from="@tanstack/react-router" preload="intent"> on internal links — never plain <a href>.
The worker entry applies Cloudflare edge cache profiles (defined in @decocms/start/sdk/cacheHeaders):
| URL pattern | Profile | Edge TTL |
|---|---|---|
/ |
static | 1 day |
*/p |
product | 5 min |
/s, ?q= |
search | 60s |
/cart, /checkout |
private | none |
| Everything else | listing | 2 min |
Override per-route in src/cache-config.ts.
Cloudflare Workers via Wrangler. Configuration is in wrangler.jsonc (entry: src/worker-entry.ts).
Two GitHub Actions workflows handle CI/CD automatically (see .github/workflows/README.md):
preview.yml— on every PR, uploads a versioned preview viawrangler versions upload --preview-aliasand posts the URL as a sticky comment. Skips gracefully if Cloudflare secrets aren't configured.deploy.yml— on push tomain, runswrangler deploywithBUILD_HASHinjected.
Required repo secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID.
To deploy manually from your machine:
npm run deployFor Argo CD / Kubernetes deployment manifests see deploy/.
This repo was itself produced by the migration flow above. Two generated docs are kept for reference:
MIGRATION_REPORT.md— files scaffolded / transformed / deleted, manual review itemsMIGRATION_NEXT_STEPS.md— open follow-up work and canonical patterns to follow
- deco.cx docs
- Discord
- Framework source:
@decocms/start,@decocms/apps
MIT