Upgrading from @decocms/start 6.x
Move a TanStack Start site from the single @decocms/start 6.x and @decocms/apps 5.x packages to the split v7 packages.
Before v7, the framework shipped as two large packages: @decocms/start and @decocms/apps. v7 splits them into @decocms/blocks, @decocms/blocks-admin, @decocms/blocks-cli, @decocms/tanstack and one @decocms/apps-* package per platform. The functions are mostly the same; their import paths change. This page is for sites already on TanStack Start with @decocms/start 6.x and @decocms/apps 5.x in package.json. A codemod does the mechanical part; the rest is a short list of manual changes and checks.
If your site is still on Fresh and Deno, see Migrating from Fresh and Deno. For a Next.js site on @decocms/start 5.x, see Moving a Next.js site off @decocms/start 5.x.
Plan the work as four commits
Each step leaves the repository in a state you can review and type-check on its own:
- Dependencies. Swap the packages and the
generatescript. - Imports. Run the codemod; no behaviour changes.
- Generated files. Move generated artifacts into
.deco/. - Bump and verify. Pin versions, regenerate, run the checks.
Using an AI coding agent? The repository ships an Agent Skill for this upgrade, a folder of instructions an agent loads. Install it with the skills CLI: npx skills add decocms/blocks --skill decocms-v6-to-v7-upgrade.
Before you start, record the current type-check output (tsc --noEmit) on the unchanged branch. Many sites have pre-existing errors; the goal is no new errors, not zero.
1. Swap the dependencies
Remove @decocms/start and @decocms/apps. Add the framework packages:
bun add @decocms/blocks @decocms/blocks-admin @decocms/tanstackbun add -d @decocms/blocks-cliThen add only the app packages the site imports. List them with:
grep -rhoE '@decocms/apps/[a-z-]+' src/ | sort -uEach @decocms/apps/<vendor> becomes @decocms/apps-<vendor>. Almost every site also needs @decocms/apps-commerce (shared types and helpers) and @decocms/apps-website (SEO and analytics sections). The repository's own guidance targets 7.6.0 or later; 7.7.0 or later removes two workarounds noted below.
In the same commit:
-
Replace the chain of
generate:*scripts with the single orchestrator, folding in any per-generator flags (--exclude,--namespace,--skip-apps,--platform):package.json { "scripts": { "generate": "tsx node_modules/@decocms/blocks-cli/scripts/generate.ts --site my-store", "build": "npm run generate && tsr generate && vite build" } }Commit
.deco/generate.digests.json, which it writes, so fresh clones and CI skip unchanged generators..deco/.cache/stays out of git (see Code generation). -
Update
resolve.dedupeinvite.config.ts: replace@decocms/startand@decocms/appswith the new package names. -
Delete a stale
package-lock.jsonif the site uses Bun. An npm install from an old lockfile installs the 6.x packages again, and the upgrade silently doesn't take effect.
2. Rewrite the imports
The deco-upgrade-6-to-7 codemod rewrites import paths in src/ and updates package.json. Run it without flags first to see what it would change, then apply:
npx -p @decocms/blocks-cli deco-upgrade-6-to-7npx -p @decocms/blocks-cli deco-upgrade-6-to-7 --write--src-dir <dir> scans another directory. The codemod adds the framework packages and @decocms/apps-commerce to package.json at latest; pin exact versions after installing. It prints lines starting with MANUAL: for changes it can't make itself.
The import mapping
| Before (6.x) | After (v7) |
|---|---|
@decocms/start/sdk/<name> | @decocms/blocks/sdk/<name> (same subpath) |
@decocms/start/cms, server code | @decocms/blocks/cms |
@decocms/start/cms, client code (getSection, getSectionRegistry) | @decocms/blocks/cms/client |
@decocms/start/setup | @decocms/blocks/setup + @decocms/blocks-admin/setup (see below) |
@decocms/start/types/widgets | @decocms/blocks/types/widgets |
@decocms/start root (types) | @decocms/blocks/types |
@decocms/start/matchers/builtins | Usually delete it (see below) |
@decocms/start/admin | @decocms/blocks-admin |
@decocms/start/routes: cmsRouteConfig, cmsHomeRouteConfig, loadCmsPage, loadCmsHomePage, loadDeferredSection, withSiteGlobals | @decocms/tanstack |
@decocms/start/routes: decoMetaRoute, decoRenderRoute, decoInvokeRoute | decoMetaRouteConfig(), decoRenderRouteConfig(), decoInvokeRouteConfig() from @decocms/tanstack (now factories) |
@decocms/start/routes: deferredSectionLoader | @decocms/tanstack/sdk/deferredSectionLoader |
@decocms/start/hooks: DecoPageRenderer, DecoRootLayout, SectionRenderer, PreviewProviders | @decocms/tanstack |
@decocms/start/hooks: RenderSection | @decocms/blocks/hooks (the codemod instead renames it to SectionRenderer from @decocms/tanstack; either works) |
@decocms/start/sdk/router (createDecoRouter), @decocms/start/sdk/workerEntry (createDecoWorkerEntry) | @decocms/tanstack |
@decocms/start/vite | @decocms/tanstack/vite |
@decocms/start/sdk/cookiePassthrough | @decocms/tanstack/sdk/cookiePassthrough |
@decocms/start/sdk/createInvoke | @decocms/tanstack/sdk/createInvoke |
@decocms/start/sdk/useHydrated | useHydrated from @tanstack/react-router |
@decocms/apps/<vendor>/<path> | @decocms/apps-<vendor>/<path> |
@decocms/apps/commerce/{sdk,types,utils}/* | @decocms/apps-commerce/* |
@decocms/apps/commerce/components/{Image,Picture,JsonLd} | @decocms/blocks/hooks |
@decocms/apps/website/* | @decocms/apps-website/* |
@decocms/apps/registry (APP_REGISTRY) | Each app's own @decocms/apps-<vendor>/registry entry (see below) |
The codemod covers the framework paths and @decocms/apps/{commerce,vtex}; rewrite the other @decocms/apps/<vendor> paths by hand with the same rule.
Split the setup call
v6's setup took every option in one call. In v7, the framework half and the admin half are separate functions in separate packages:
import { createSiteSetup } from "@decocms/blocks/setup";
import { createAdminSetup } from "@decocms/blocks-admin/setup";
import { PreviewProviders } from "@decocms/tanstack";
import { blocks } from "../.deco/blocks.gen";
import appCss from "./styles/app.css?url";
createSiteSetup({
sections: import.meta.glob("./sections/**/*.tsx"),
blocks,
productionOrigins: ["https://www.example.com"],
});
createAdminSetup({
meta: () => import("../.deco/meta.gen.json").then((m) => m.default),
css: appCss,
previewWrapper: PreviewProviders,
});createSiteSetuptakessections,blocks,productionOrigins,customMatchers,onResolveError,onDanglingReferenceandinitPlatform.createAdminSetuptakesmeta,css,fonts,previewWrapperandgetCommerceLoaders.- Remove
customMatchers: [registerBuiltinMatchers]and its import:createSiteSetupalways registers the built-in matchers.
Use the admin route factories
The admin routes are now factories. Call them in each route file, and never pass a shared object:
import { createFileRoute } from "@tanstack/react-router";
import { decoMetaRouteConfig } from "@decocms/tanstack";
export const Route = createFileRoute("/deco/meta")(decoMetaRouteConfig());Same for /deco/render with decoRenderRouteConfig() and /deco/invoke/$ with decoInvokeRouteConfig(). These factories exist from 7.10.0.
Rebuild the app registry
6.x exported one aggregate APP_REGISTRY from @decocms/apps/registry. v7 has none: each app package exports its own entry from ./registry, and you list the ones whose loaders appear in your content. Pass the modules statically, because the entries' dynamic imports can fail in the production Worker bundle:
import { autoconfigApps, type AppRegistry } from "@decocms/blocks-admin/apps";
import { VTEX_REGISTRY_ENTRY } from "@decocms/apps-vtex/registry";
import * as vtexMod from "@decocms/apps-vtex/mod";
const APP_REGISTRY: AppRegistry = [{ ...VTEX_REGISTRY_ENTRY, module: async () => vtexMod }];
export const setupApps = (blocks: Record<string, unknown>) => autoconfigApps(blocks, APP_REGISTRY);To see which app namespaces your content uses:
grep -rhoE '"(shopify|vtex|commerce|website|algolia|magento|salesforce)/[^"]+"' .deco/blocks/ | sort -uA namespace with no configured app leaves its loaders unresolved, and their sections render empty. See Apps.
Remove old shims
On 7.7.0 or later, delete local copies of deferredSectionLoader and pass the public one to DecoPageRenderer. On 7.6.0 or later, use @decocms/tanstack/sdk/cookiePassthrough instead of a local cookie-passthrough shim, and import it only from server-only modules.
3. Move generated files into .deco/
v7's generators write to .deco/ instead of src/server/:
git mv src/server/cms/blocks.gen.json .deco/blocks.gen.jsonDo the same for blocks.gen.ts, loaders.gen.ts and src/server/admin/meta.gen.json (to .deco/meta.gen.json), regenerate sections.gen.ts into .deco/, point the imports in src/setup.ts at ../.deco/…, and delete the empty src/server/cms/ and src/server/admin/ directories.
src/server/invoke.gen.ts stays in src/. TanStack Start's compiler must see it there; moved under .deco/, every /_serverFn call fails on the server.
Don't name any file of your own *blocks.gen.ts: the Vite plugin replaces modules with that suffix with an empty stub in the client bundle.
4. Bump, regenerate and verify
Pin the final versions, install, and regenerate everything (a version change invalidates the generate cache):
bun installbun run generate --forceThen run these checks. A clean type-check alone isn't enough.
- Clean install. The lockfile contains no
@decocms/startor@decocms/appsentries. - Regeneration is stable. After
generate --forceand a build,git statusshows no changes. - No new type errors compared with the baseline you recorded.
- The build passes.
bun run buildcatches server-only imports that reach the client bundle. - Dev smoke test.
bun run dev, then/renders, and/live/_metaand/.decofilereturn JSON. - The production build works. Run
bun run previewand load a real product page and listing page in a browser, checking that products render. Commerce sections are often deferred, and some failures (such as app modules that don't resolve) only happen in the production bundle. - Parity with production.
/live/_metamatches the deployed site's schema, and/.decofilediffers only by content changes you expect.
Version notes
The repository records these changes within 7.x:
| Version | Change |
|---|---|
| 7.6.0 | getRequestCookieHeader and forwardResponseCookies public at @decocms/tanstack/sdk/cookiePassthrough. |
| 7.7.0 | deferredSectionLoader public at @decocms/tanstack/sdk/deferredSectionLoader. generate finds the VTEX app's invoke file without --apps-dir, and generated sections.gen.ts declares neverDefer. |
| 7.10.0 | Admin routes exist only as factories (decoMetaRouteConfig() and friends). |
7.11.2 (@decocms/blocks) | Loader keys with a .ts suffix fall back to the extension-less registration. |
Related
- Packages and exports: every v7 import path.
- Troubleshooting
- CLI reference