@decocms/start 0.39.0 → 0.40.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 (33) hide show
  1. package/.agents/skills/deco-migrate-script/SKILL.md +434 -0
  2. package/.agents/skills/deco-to-tanstack-migration/SKILL.md +382 -0
  3. package/.agents/skills/deco-to-tanstack-migration/references/admin-cms.md +154 -0
  4. package/{.cursor/skills/deco-async-rendering-site-guide/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/async-rendering.md} +296 -31
  5. package/.agents/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  6. package/.agents/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  7. package/.agents/skills/deco-to-tanstack-migration/references/css-styling.md +156 -0
  8. package/.agents/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  9. package/.agents/skills/deco-to-tanstack-migration/references/gotchas.md +13 -0
  10. package/{.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/hydration-fixes.md} +139 -4
  11. package/.agents/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  12. package/{.cursor/skills/deco-islands-migration/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/islands.md} +0 -14
  13. package/.agents/skills/deco-to-tanstack-migration/references/jsx-migration.md +80 -0
  14. package/.agents/skills/deco-to-tanstack-migration/references/matchers.md +1064 -0
  15. package/{.cursor/skills/deco-tanstack-navigation/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/navigation.md} +1 -16
  16. package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  17. package/.agents/skills/deco-to-tanstack-migration/references/react-hooks-patterns.md +142 -0
  18. package/.agents/skills/deco-to-tanstack-migration/references/react-signals-state.md +72 -0
  19. package/{.cursor/skills/deco-tanstack-search/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/search.md} +1 -13
  20. package/.agents/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  21. package/{.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/storefront-patterns.md} +1 -137
  22. package/.agents/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  23. package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +165 -0
  24. package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +209 -0
  25. package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  26. package/.agents/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  27. package/.agents/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  28. package/.agents/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  29. package/.agents/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  30. package/.agents/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  31. package/README.md +45 -0
  32. package/package.json +1 -1
  33. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +0 -270
@@ -0,0 +1,110 @@
1
+ # __root.tsx Template
2
+
3
+ ```typescript
4
+ import { useState } from "react";
5
+ import {
6
+ createRootRoute,
7
+ HeadContent,
8
+ Outlet,
9
+ Scripts,
10
+ useRouterState,
11
+ } from "@tanstack/react-router";
12
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
13
+ import { LiveControls } from "@decocms/start/hooks";
14
+ import { ANALYTICS_SCRIPT } from "@decocms/start/sdk/analytics";
15
+ import appCss from "../styles/app.css?url";
16
+
17
+ // Deco analytics event dispatcher — must be in <head> before any section renders
18
+ const DECO_EVENTS_BOOTSTRAP = `
19
+ window.DECO = window.DECO || {};
20
+ window.DECO.events = window.DECO.events || {
21
+ _q: [],
22
+ dispatch: function(e) {
23
+ if (window.dataLayer) { window.dataLayer.push({ event: e.name, ecommerce: e.params }); }
24
+ this._q.push(e);
25
+ }
26
+ };
27
+ window.dataLayer = window.dataLayer || [];
28
+ `;
29
+
30
+ // Navigation progress bar CSS
31
+ const PROGRESS_CSS = `
32
+ @keyframes decoProgress{0%{width:0}30%{width:50%}60%{width:80%}100%{width:98%}}
33
+ .deco-nav-progress{position:fixed;top:0;left:0;height:3px;background:var(--color-primary,#e53e3e);z-index:9999;animation:decoProgress 4s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;box-shadow:0 0 8px var(--color-primary,#e53e3e)}
34
+ @keyframes decoFadeIn{from{opacity:0}to{opacity:1}}
35
+ .deco-nav-overlay{position:fixed;inset:0;z-index:9998;pointer-events:none;background:rgba(255,255,255,0.15);animation:decoFadeIn .2s ease-out}
36
+ `;
37
+
38
+ function NavigationProgress() {
39
+ const isLoading = useRouterState({ select: (s) => s.isLoading });
40
+ if (!isLoading) return null;
41
+ return (
42
+ <>
43
+ <style dangerouslySetInnerHTML={{ __html: PROGRESS_CSS }} />
44
+ <div className="deco-nav-progress" />
45
+ <div className="deco-nav-overlay" />
46
+ </>
47
+ );
48
+ }
49
+
50
+ export const Route = createRootRoute({
51
+ head: () => ({
52
+ meta: [
53
+ { charSet: "utf-8" },
54
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
55
+ { title: "My Store" },
56
+ ],
57
+ links: [
58
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
59
+ { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
60
+ // Add your font stylesheet here:
61
+ // { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=..." },
62
+ { rel: "stylesheet", href: appCss },
63
+ { rel: "icon", href: "/favicon.ico" },
64
+ ],
65
+ }),
66
+ component: RootLayout,
67
+ });
68
+
69
+ function RootLayout() {
70
+ const [queryClient] = useState(
71
+ () =>
72
+ new QueryClient({
73
+ defaultOptions: {
74
+ queries: {
75
+ staleTime: import.meta.env.DEV ? 0 : 30_000,
76
+ gcTime: import.meta.env.DEV ? 0 : 5 * 60_000,
77
+ refetchOnWindowFocus: import.meta.env.DEV,
78
+ },
79
+ },
80
+ })
81
+ );
82
+
83
+ return (
84
+ <html lang="pt-BR" data-theme="light" suppressHydrationWarning>
85
+ <head>
86
+ <script dangerouslySetInnerHTML={{ __html: DECO_EVENTS_BOOTSTRAP }} />
87
+ <HeadContent />
88
+ </head>
89
+ <body className="bg-base-100 text-base-content" suppressHydrationWarning>
90
+ <NavigationProgress />
91
+ <QueryClientProvider client={queryClient}>
92
+ <Outlet />
93
+ </QueryClientProvider>
94
+ <LiveControls site={process.env.DECO_SITE_NAME} />
95
+ <script type="module" dangerouslySetInnerHTML={{ __html: ANALYTICS_SCRIPT }} />
96
+ <Scripts />
97
+ </body>
98
+ </html>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Key Points
104
+
105
+ 1. **QueryClientProvider** — Required even if not using React Query directly. @decocms/apps hooks may use it.
106
+ 2. **LiveControls** — Admin iframe bridge. `site` prop must match CMS site name.
107
+ 3. **DECO_EVENTS_BOOTSTRAP** — Must be in `<head>` before sections. Sections dispatch analytics events on render.
108
+ 4. **NavigationProgress** — Visual feedback during client-side navigation.
109
+ 5. **suppressHydrationWarning** — On `<html>` and `<body>` to avoid mismatches from browser extensions.
110
+ 6. **data-theme="light"** — Required for DaisyUI v4/v5 CSS variables to activate.
@@ -0,0 +1,96 @@
1
+ # router.tsx Template
2
+
3
+ ```typescript
4
+ import { createRouter as createTanStackRouter } from "@tanstack/react-router";
5
+ import { routeTree } from "./routeTree.gen";
6
+
7
+ export function createRouter() {
8
+ const router = createTanStackRouter({
9
+ routeTree,
10
+ scrollRestoration: true,
11
+ });
12
+
13
+ // Scroll to top on forward navigation (PUSH/REPLACE), skip on back/forward (GO)
14
+ router.subscribe("onResolved", (evt) => {
15
+ if (evt.type === "GO") return;
16
+ window.scrollTo({ top: 0 });
17
+ });
18
+
19
+ return router;
20
+ }
21
+
22
+ declare module "@tanstack/react-router" {
23
+ interface Register {
24
+ router: ReturnType<typeof createRouter>;
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Route Files
30
+
31
+ ### src/routes/index.tsx (Homepage)
32
+
33
+ ```typescript
34
+ import { createFileRoute } from "@tanstack/react-router";
35
+ import { cmsHomeRouteConfig } from "@decocms/start/routes";
36
+ import { DecoPageRenderer } from "@decocms/start/hooks";
37
+ import { loadDeferredSection } from "@decocms/start/routes/cmsRoute";
38
+
39
+ const { loader, head } = cmsHomeRouteConfig({
40
+ defaultTitle: "My Store",
41
+ });
42
+
43
+ export const Route = createFileRoute("/")({
44
+ loader,
45
+ head,
46
+ component: HomePage,
47
+ });
48
+
49
+ function HomePage() {
50
+ const { resolvedSections, deferredSections, pagePath } = Route.useLoaderData();
51
+ return (
52
+ <DecoPageRenderer
53
+ sections={resolvedSections}
54
+ deferredSections={deferredSections}
55
+ pagePath={pagePath}
56
+ loadDeferredSectionFn={loadDeferredSection}
57
+ />
58
+ );
59
+ }
60
+ ```
61
+
62
+ ### src/routes/$.tsx (Catch-All CMS Route)
63
+
64
+ ```typescript
65
+ import { createFileRoute, notFound } from "@tanstack/react-router";
66
+ import { cmsRouteConfig } from "@decocms/start/routes";
67
+ import { DecoPageRenderer } from "@decocms/start/hooks";
68
+ import { loadDeferredSection } from "@decocms/start/routes/cmsRoute";
69
+
70
+ const { loader, head } = cmsRouteConfig({
71
+ siteName: "My Store",
72
+ ignoreSearchParams: ["skuId"],
73
+ });
74
+
75
+ export const Route = createFileRoute("/$")({
76
+ loader: async (ctx) => {
77
+ const data = await loader(ctx);
78
+ if (!data) throw notFound();
79
+ return data;
80
+ },
81
+ head,
82
+ component: CmsPage,
83
+ });
84
+
85
+ function CmsPage() {
86
+ const { resolvedSections, deferredSections, pagePath } = Route.useLoaderData();
87
+ return (
88
+ <DecoPageRenderer
89
+ sections={resolvedSections}
90
+ deferredSections={deferredSections}
91
+ pagePath={pagePath}
92
+ loadDeferredSectionFn={loadDeferredSection}
93
+ />
94
+ );
95
+ }
96
+ ```
@@ -0,0 +1,167 @@
1
+ # setup.ts Template
2
+
3
+ Annotated template based on espacosmart-storefront (100+ sections, VTEX, async rendering).
4
+
5
+ ```typescript
6
+ // ==========================================================================
7
+ // 1. CMS BLOCKS & META
8
+ // ==========================================================================
9
+
10
+ import blocksJson from "./server/cms/blocks.gen.ts";
11
+ import metaData from "./server/admin/meta.gen.json";
12
+ import { setBlocks } from "@decocms/start/cms/loader";
13
+ import { setMetaData, setInvokeLoaders, setRenderShell } from "@decocms/start/admin";
14
+ import { registerSections, registerSectionsSync, setResolvedComponent } from "@decocms/start/cms/registry";
15
+ import { registerSectionLoaders, registerLayoutSections } from "@decocms/start/cms/sectionLoaders";
16
+ import { registerCommerceLoaders, setAsyncRenderingConfig, onBeforeResolve } from "@decocms/start/cms/resolve";
17
+ import { createCachedLoader } from "@decocms/start/sdk/cachedLoader";
18
+ import appCss from "./styles/app.css?url";
19
+
20
+ // Load CMS blocks (pages, sections, configs) from generated JSON
21
+ setBlocks(blocksJson);
22
+
23
+ // Set admin schema for /live/_meta endpoint
24
+ setMetaData(metaData);
25
+
26
+ // Configure admin preview HTML shell
27
+ setRenderShell({
28
+ css: appCss,
29
+ fonts: ["https://fonts.googleapis.com/css2?family=YourFont:wght@400;500;600;700&display=swap"],
30
+ theme: "light", // data-theme="light" on <html> for DaisyUI
31
+ bodyClass: "bg-base-100 text-base-content",
32
+ lang: "pt-BR",
33
+ });
34
+
35
+ // ==========================================================================
36
+ // 2. SECTION REGISTRATION
37
+ // ==========================================================================
38
+
39
+ // Critical sections — above-the-fold, bundled synchronously
40
+ import HeaderSection from "./components/header/Header";
41
+ import FooterSection from "./sections/Footer/Footer";
42
+
43
+ const criticalSections: Record<string, any> = {
44
+ "site/sections/Header/Header.tsx": HeaderSection,
45
+ "site/sections/Footer/Footer.tsx": FooterSection,
46
+ };
47
+
48
+ // Register sync components for instant SSR (no Suspense boundary)
49
+ for (const [key, mod] of Object.entries(criticalSections)) {
50
+ setResolvedComponent(key, mod.default || mod);
51
+ }
52
+ registerSectionsSync(criticalSections);
53
+
54
+ // All sections — lazy-loaded via dynamic import
55
+ registerSections({
56
+ "site/sections/Header/Header.tsx": () => import("./sections/Header/Header"),
57
+ "site/sections/Footer/Footer.tsx": () => import("./sections/Footer/Footer"),
58
+ "site/sections/Theme/Theme.tsx": () => import("./sections/Theme/Theme"),
59
+ // ... register ALL sections from src/sections/ here
60
+ // Pattern: "site/sections/Path/Name.tsx": () => import("./sections/Path/Name")
61
+ });
62
+
63
+ // ==========================================================================
64
+ // 3. LAYOUT SECTIONS
65
+ // ==========================================================================
66
+
67
+ // Layout sections are always rendered eagerly (never lazy/deferred),
68
+ // even if wrapped in Lazy.tsx in the CMS.
69
+ registerLayoutSections([
70
+ "site/sections/Header/Header.tsx",
71
+ "site/sections/Footer/Footer.tsx",
72
+ "site/sections/Theme/Theme.tsx",
73
+ "site/sections/Social/WhatsApp.tsx",
74
+ ]);
75
+
76
+ // ==========================================================================
77
+ // 4. SECTION LOADERS
78
+ // ==========================================================================
79
+
80
+ // Section loaders enrich CMS props with server-side data (e.g., VTEX API calls).
81
+ // Only needed for sections that export `const loader`.
82
+ registerSectionLoaders({
83
+ "site/sections/Product/ProductShelf.tsx": (props: any, req: Request) =>
84
+ import("./components/product/ProductShelf").then((m) => m.loader(props, req)),
85
+ "site/sections/Product/SearchResult.tsx": (props: any, req: Request) =>
86
+ import("./components/search/SearchResult").then((m) => m.loader(props, req)),
87
+ // ... add for each section that has `export const loader`
88
+ });
89
+
90
+ // ==========================================================================
91
+ // 5. COMMERCE LOADERS (VTEX)
92
+ // ==========================================================================
93
+
94
+ import { vtexProductList } from "@decocms/apps/vtex/loaders/productList";
95
+ import { vtexProductDetailsPage } from "@decocms/apps/vtex/loaders/productDetailsPage";
96
+ import { vtexProductListingPage } from "@decocms/apps/vtex/loaders/productListingPage";
97
+ import { vtexSuggestions } from "@decocms/apps/vtex/loaders/suggestions";
98
+ import { initVtexFromBlocks } from "@decocms/apps/vtex/setup";
99
+
100
+ // SWR-cached commerce loaders — avoids re-fetching on every page navigation
101
+ const cachedProductList = createCachedLoader("vtex/productList", vtexProductList, {
102
+ policy: "stale-while-revalidate", maxAge: 60_000,
103
+ });
104
+ const cachedPDP = createCachedLoader("vtex/pdp", vtexProductDetailsPage, {
105
+ policy: "stale-while-revalidate", maxAge: 30_000,
106
+ });
107
+ const cachedPLP = createCachedLoader("vtex/plp", vtexProductListingPage, {
108
+ policy: "stale-while-revalidate", maxAge: 60_000,
109
+ });
110
+ const cachedSuggestions = createCachedLoader("vtex/suggestions", vtexSuggestions, {
111
+ policy: "stale-while-revalidate", maxAge: 120_000,
112
+ });
113
+
114
+ // Map CMS __resolveType strings to actual loader functions
115
+ registerCommerceLoaders({
116
+ "vtex/loaders/intelligentSearch/productList.ts": cachedProductList,
117
+ "vtex/loaders/intelligentSearch/productListingPage.ts": cachedPLP,
118
+ "vtex/loaders/intelligentSearch/productDetailsPage.ts": cachedPDP,
119
+ "vtex/loaders/intelligentSearch/suggestions.ts": cachedSuggestions,
120
+ // Add passthrough loaders for types that don't need caching:
121
+ // "vtex/loaders/config.ts": (props) => props,
122
+ });
123
+
124
+ // ==========================================================================
125
+ // 6. VTEX INITIALIZATION
126
+ // ==========================================================================
127
+
128
+ // onBeforeResolve runs once before the first CMS page resolution.
129
+ // initVtexFromBlocks reads VTEX config (account, publicUrl) from CMS blocks.
130
+ onBeforeResolve(() => {
131
+ initVtexFromBlocks();
132
+ });
133
+
134
+ // ==========================================================================
135
+ // 7. ASYNC RENDERING
136
+ // ==========================================================================
137
+
138
+ // Enable deferred section loading (scroll-triggered).
139
+ // Respects CMS Lazy wrappers. Layout sections and alwaysEager are never deferred.
140
+ setAsyncRenderingConfig({
141
+ alwaysEager: [
142
+ "site/sections/Header/Header.tsx",
143
+ "site/sections/Footer/Footer.tsx",
144
+ "site/sections/Theme/Theme.tsx",
145
+ "site/sections/Images/Carousel.tsx",
146
+ // Add above-the-fold sections here
147
+ ],
148
+ });
149
+
150
+ // ==========================================================================
151
+ // 8. INVOKE LOADERS (for /deco/invoke endpoint)
152
+ // ==========================================================================
153
+
154
+ setInvokeLoaders({
155
+ "vtex/loaders/intelligentSearch/productList.ts": cachedProductList,
156
+ "vtex/loaders/intelligentSearch/suggestions.ts": cachedSuggestions,
157
+ // Used by the admin to preview loader results
158
+ });
159
+ ```
160
+
161
+ ## Key Patterns
162
+
163
+ 1. **Order matters**: blocks → meta → sections → loaders → commerce → async config
164
+ 2. **Critical sections**: Import synchronously for instant SSR, also register as lazy for client code-splitting
165
+ 3. **SWR caching**: `createCachedLoader` wraps commerce loaders with stale-while-revalidate
166
+ 4. **onBeforeResolve**: Deferred initialization — VTEX config is read from CMS blocks at first request
167
+ 5. **alwaysEager**: Sections that must render on first paint (no deferred loading)
@@ -0,0 +1,122 @@
1
+ # vite.config.ts Template
2
+
3
+ Battle-tested configuration from espacosmart-storefront.
4
+
5
+ ```typescript
6
+ import { cloudflare } from "@cloudflare/vite-plugin";
7
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
8
+ import react from "@vitejs/plugin-react";
9
+ import tailwindcss from "@tailwindcss/vite";
10
+ import { defineConfig } from "vite";
11
+ import path from "path";
12
+
13
+ const srcDir = path.resolve(__dirname, "src");
14
+
15
+ export default defineConfig({
16
+ plugins: [
17
+ cloudflare({ viteEnvironment: { name: "ssr" } }),
18
+ tanstackStart({ server: { entry: "server" } }),
19
+ react({
20
+ babel: {
21
+ plugins: [
22
+ ["babel-plugin-react-compiler", { target: "19" }],
23
+ ],
24
+ },
25
+ }),
26
+ tailwindcss(),
27
+ // CRITICAL: Stubs for client bundles that transitively import server modules.
28
+ // Without this, client build crashes on node:async_hooks, react-dom/server, etc.
29
+ {
30
+ name: "deco-server-only-stubs",
31
+ enforce: "pre" as const,
32
+ resolveId(id, _importer, options) {
33
+ if (options?.ssr) return undefined;
34
+ const CLIENT_STUBS: Record<string, string> = {
35
+ "react-dom/server": "\0stub:react-dom-server",
36
+ "react-dom/server.browser": "\0stub:react-dom-server",
37
+ "node:stream": "\0stub:node-stream",
38
+ "node:stream/web": "\0stub:node-stream-web",
39
+ "node:async_hooks": "\0stub:node-async-hooks",
40
+ "tanstack-start-injected-head-scripts:v": "\0stub:tanstack-head-scripts",
41
+ };
42
+ return CLIENT_STUBS[id];
43
+ },
44
+ configEnvironment(name: string, env: any) {
45
+ if (name === "ssr" || name === "client") {
46
+ env.optimizeDeps = env.optimizeDeps || {};
47
+ env.optimizeDeps.esbuildOptions = env.optimizeDeps.esbuildOptions || {};
48
+ env.optimizeDeps.esbuildOptions.jsx = "automatic";
49
+ env.optimizeDeps.esbuildOptions.jsxImportSource = "react";
50
+ }
51
+ },
52
+ load(id) {
53
+ if (id === "\0stub:react-dom-server") {
54
+ return [
55
+ "const noop = () => '';",
56
+ "export const renderToString = noop;",
57
+ "export const renderToStaticMarkup = noop;",
58
+ "export const renderToReadableStream = noop;",
59
+ "export const resume = noop;",
60
+ "export const version = '19.0.0';",
61
+ "export default { renderToString: noop, renderToStaticMarkup: noop, renderToReadableStream: noop, resume: noop, version: '19.0.0' };",
62
+ ].join("\n");
63
+ }
64
+ if (id === "\0stub:node-stream") {
65
+ return "export class PassThrough {}; export class Readable {}; export class Writable {}; export default { PassThrough, Readable, Writable };";
66
+ }
67
+ if (id === "\0stub:node-stream-web") {
68
+ return "export const ReadableStream = globalThis.ReadableStream; export const WritableStream = globalThis.WritableStream; export const TransformStream = globalThis.TransformStream; export default { ReadableStream, WritableStream, TransformStream };";
69
+ }
70
+ if (id === "\0stub:node-async-hooks") {
71
+ return [
72
+ "class _ALS { getStore() { return undefined; } run(_store, fn, ...args) { return fn(...args); } enterWith() {} disable() {} }",
73
+ "export const AsyncLocalStorage = _ALS;",
74
+ "export const AsyncResource = class {};",
75
+ "export function executionAsyncId() { return 0; }",
76
+ "export function createHook() { return { enable() {}, disable() {} }; }",
77
+ "export default { AsyncLocalStorage: _ALS, AsyncResource, executionAsyncId, createHook };",
78
+ ].join("\n");
79
+ }
80
+ if (id === "\0stub:tanstack-head-scripts") {
81
+ return "export const injectedHeadScripts = undefined;";
82
+ }
83
+ },
84
+ },
85
+ ],
86
+ // Inject site name at build time (not runtime)
87
+ define: {
88
+ "process.env.DECO_SITE_NAME": JSON.stringify(
89
+ process.env.DECO_SITE_NAME || "my-store"
90
+ ),
91
+ },
92
+ esbuild: {
93
+ jsx: "automatic",
94
+ jsxImportSource: "react",
95
+ },
96
+ resolve: {
97
+ // CRITICAL: Without dedupe, multiple React/TanStack instances cause hook errors
98
+ dedupe: [
99
+ "@tanstack/react-start",
100
+ "@tanstack/react-router",
101
+ "@tanstack/react-start-server",
102
+ "@tanstack/start-server-core",
103
+ "@tanstack/start-client-core",
104
+ "@tanstack/start-plugin-core",
105
+ "@tanstack/start-storage-context",
106
+ "react",
107
+ "react-dom",
108
+ ],
109
+ alias: {
110
+ "~": srcDir,
111
+ },
112
+ },
113
+ });
114
+ ```
115
+
116
+ ## Key Points
117
+
118
+ 1. **deco-server-only-stubs plugin** — Required. Client bundles transitively import `node:async_hooks`, `react-dom/server`, etc. Without stubs, build crashes.
119
+ 2. **resolve.dedupe** — Required. Without it, multiple React instances cause "Invalid hook call" errors.
120
+ 3. **process.env.DECO_SITE_NAME via define** — Must be injected at build time, not read at runtime.
121
+ 4. **React Compiler** — `babel-plugin-react-compiler` with target 19 for automatic memoization.
122
+ 5. **esbuild.jsx** — Must be `"automatic"` with `jsxImportSource: "react"` for proper JSX transform.
@@ -0,0 +1,67 @@
1
+ # Worker Entry Templates
2
+
3
+ ## src/server.ts
4
+
5
+ ```typescript
6
+ // CRITICAL: import "./setup" MUST be the first import.
7
+ // Without it, server functions in Vite split modules have empty state
8
+ // (blocks, registry, commerce loaders) causing 404 on client-side navigation.
9
+ import "./setup";
10
+ import { createStartHandler, defaultStreamHandler } from "@tanstack/react-start/server";
11
+
12
+ export default createStartHandler(defaultStreamHandler);
13
+ ```
14
+
15
+ ## src/worker-entry.ts
16
+
17
+ ```typescript
18
+ // CRITICAL: import "./setup" MUST be the first import.
19
+ import "./setup";
20
+ import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
21
+ import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
22
+ import {
23
+ handleMeta,
24
+ handleDecofileRead,
25
+ handleDecofileReload,
26
+ handleRender,
27
+ corsHeaders,
28
+ } from "@decocms/start/admin";
29
+ // Only if using VTEX:
30
+ import { shouldProxyToVtex, proxyToVtex } from "@decocms/apps/vtex/utils/proxy";
31
+
32
+ const serverEntry = createServerEntry({
33
+ async fetch(request) {
34
+ return await handler.fetch(request);
35
+ },
36
+ });
37
+
38
+ export default createDecoWorkerEntry(serverEntry, {
39
+ admin: {
40
+ handleMeta,
41
+ handleDecofileRead,
42
+ handleDecofileReload,
43
+ handleRender,
44
+ corsHeaders,
45
+ },
46
+ // VTEX proxy — routes like /api/*, /checkout/*, /arquivos/* go to VTEX
47
+ proxyHandler: (request, url) => {
48
+ if (shouldProxyToVtex(url.pathname)) {
49
+ return proxyToVtex(request);
50
+ }
51
+ return null;
52
+ },
53
+ });
54
+ ```
55
+
56
+ ## Key Rules
57
+
58
+ 1. **`import "./setup"` is ALWAYS the first line** — both files. This registers sections, loaders, blocks, and commerce config before any server function executes.
59
+ 2. **Admin handlers go in `createDecoWorkerEntry`** — NOT inside `createServerEntry`. TanStack Start's build strips custom fetch logic from `createServerEntry` in production.
60
+ 3. **Proxy handler** — Optional. Only needed for platforms (VTEX, Shopify) that require server-side proxying.
61
+ 4. **Request flow**: `createDecoWorkerEntry` → admin routes (first) → cache check → proxy check → `serverEntry.fetch()` (TanStack Start).
62
+
63
+ ## Why `import "./setup"` Must Be First
64
+
65
+ TanStack Start compiles `createServerFn()` calls into "split modules" — separate Vite module instances. Module-level state (blockData, commerceLoaders, sectionRegistry) initialized in `setup.ts` only exists in the original module instance. Without importing setup first, these split modules execute with empty state.
66
+
67
+ The fix in `@decocms/start` uses `globalThis.__deco` to share state across all module instances. But `setup.ts` must run BEFORE any server function is called — which means it must be imported before `createStartHandler` or `createServerEntry`.
package/README.md CHANGED
@@ -62,6 +62,51 @@ Request → createDecoWorkerEntry()
62
62
  | `/cart`, `/checkout` | private | none |
63
63
  | Everything else | listing | 2 min |
64
64
 
65
+ ## Migrating from Fresh/Preact/Deno
66
+
67
+ `@decocms/start` includes an Agent Skill that handles migration for you. It works with Claude Code, Cursor, Codex, and other AI coding tools. Install the skill, open your Fresh storefront, and tell the AI to migrate:
68
+
69
+ ```bash
70
+ npx skills add decocms/deco-start
71
+ ```
72
+
73
+ Then open your project in any supported tool and say:
74
+
75
+ > migrate this project to TanStack Start
76
+
77
+ The skill handles compatibility checking, import rewrites, config generation, section registry setup, and worker entry creation. It knows what `@decocms/start` supports and will flag anything that needs manual attention.
78
+
79
+ ### Or run the script manually
80
+
81
+ ```bash
82
+ # From a new TanStack Start project with @decocms/start installed:
83
+ npx tsx node_modules/@decocms/start/scripts/migrate.ts --source /path/to/fresh-site
84
+
85
+ # Preview first without writing:
86
+ npx tsx node_modules/@decocms/start/scripts/migrate.ts --source /path/to/fresh-site --dry-run --verbose
87
+ ```
88
+
89
+ The script runs 7 phases automatically:
90
+
91
+ 1. **Analyze** — scan source, detect Preact/Fresh/Deco patterns
92
+ 2. **Scaffold** — generate `vite.config.ts`, `wrangler.jsonc`, routes, `setup.ts`, worker entry
93
+ 3. **Transform** — rewrite imports (70+ rules), JSX attrs, Fresh APIs, Deno-isms, Tailwind v3→v4
94
+ 4. **Cleanup** — delete `islands/`, old routes, `deno.json`, move `static/` → `public/`
95
+ 5. **Report** — generate `MIGRATION_REPORT.md` with manual review items
96
+ 6. **Verify** — 18+ smoke tests (zero old imports, scaffolded files exist)
97
+ 7. **Bootstrap** — `npm install`, generate CMS blocks, generate routes
98
+
99
+ Your existing `src/sections/`, `src/components/`, and `.deco/blocks/` work as-is. The script gets you to "builds clean with zero old imports" — manual work starts at platform hooks (`useCart`) and runtime tuning.
100
+
101
+ ### Agent Skills
102
+
103
+ Skills live in [`.agents/skills/`](.agents/skills/) and provide deep context to AI coding tools:
104
+
105
+ | Skill | What it covers |
106
+ |-------|---------------|
107
+ | `deco-to-tanstack-migration` | Full 12-phase migration playbook with 22 reference docs and 6 templates |
108
+ | `deco-migrate-script` | How the automated `scripts/migrate.ts` works, how to extend it |
109
+
65
110
  ## Peer Dependencies
66
111
 
67
112
  - `@tanstack/react-start` >= 1.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",