Deploying and Fast Deploy
Deploy v7 code and repository content; configure optional Fast Deploy runtime/KV delivery separately from Studio's GitHub publish workflow.
POST /.decofile endpoint below is a separate v7 capability for clients and delivery integrations, not the current Studio Publish action. See connecting a site and publishing changes.A v7 site ships two things: code (your sections, loaders and routes) and content (the decofile). By default they ship together, because the content is bundled into the build. This page explains that default, how to deploy a TanStack site to Cloudflare Workers, and Fast Deploy, an opt-in mode that serves content from Cloudflare KV so a compatible content update can reach Worker instances without a new code deploy. Current Studio publishing uses GitHub; configure the deployment integration to deliver the resulting repository content.
How content ships by default
generate turns .deco/blocks/*.json into .deco/blocks.gen.json, and the binding bundles it (see Code generation). Each build therefore carries the content that was committed when it was built, and every server starts from that snapshot.
Current Studio publishing merges content through GitHub, and the deployment integration applies it. A separate authorized runtime client can send a change to the running site with POST /.decofile (see Site Editor and the v7 admin protocol). The site swaps it into memory right away, so the next request renders the new content. But it lives only in that server's memory:
- On Cloudflare Workers, each isolate (one running copy of your Worker) holds its own copy. An isolate that starts later begins again from the bundled snapshot.
- On Next.js, each server instance holds its own copy, and a restart returns to the bundled content.
Content becomes permanent when it's committed to .deco/blocks/ and the site is rebuilt. Fast Deploy removes that gap on Workers.
Deploying a TanStack site to Workers
A TanStack site deploys with Wrangler like any Worker. The main entry of wrangler.jsonc must be your own src/worker-entry.ts, the file that calls createDecoWorkerEntry (see TanStack Start on Cloudflare Workers).
npx wrangler deploy --var BUILD_HASH:$(git rev-parse --short HEAD)BUILD_HASH identifies the build. The Worker adds it to every edge cache key, so each deploy reads and writes its own cache entries, and HTML cached by an older deploy, which points at older JavaScript chunks, is never served after a new one goes live. You can rename the variable with the cacheVersionEnv option. When it's missing, the Worker uses a hash the Vite plugin injects at build time (the commit sha on Cloudflare Workers Builds, otherwise the local git sha).
Cloudflare's docs cover the rest of a Worker's setup:
- secrets (
wrangler secret put) - staging and production environments
- custom domains
- platform limits, such as subrequests per request and memory
Fast Deploy
Fast Deploy decouples content from code on @decocms/tanstack. Content is stored in a Cloudflare KV namespace, and every isolate reads it from there, so a content update delivered to KV propagates to Worker instances and survives new isolates. Only code changes need wrangler deploy.
It isn't available in @decocms/nextjs: edge KV is specific to Cloudflare Workers.
How it works
KV holds the whole decofile as one value, keyed by deployment id: the identifier of the code version, normally its git commit sha. Keying by deployment means each code version reads only its own content. During a rolling deploy, old and new code never read each other's content, and rolling back to an older version finds its content still there.
POST /.decofile updates memory and writes the snapshot back to KVThe details:
- Rendering never waits on KV. Resolution reads content from memory, as it does without Fast Deploy. KV is touched once per isolate on its first request, and afterwards by a background check, at most once every 10 seconds, of a small revision key (a hash of the snapshot). When the revision changes, the isolate reloads the snapshot.
- The bundle is the fallback. If no deployment id resolves, or KV fails, or the key is missing, the isolate serves the content bundled with its own build. It never serves another deployment's content.
- Exact redirects live in their own keys. Redirects with an exact
frompath are stored one per KV key and looked up per request, so a site with tens of thousands of them doesn't hold them all in memory. Wildcard redirects stay in the decofile. A redirect-only change doesn't change the snapshot's revision; isolates pick it up within 60 seconds.
The deployment id comes from the DECO_DEPLOYMENT_ID variable, then BUILD_HASH, then the hash the Vite plugin injected at build time.
Turn it on
Fast Deploy requires both an explicit flag and a KV binding, so binding a namespace on its own never changes how a site behaves.
Create a KV namespace and bind it as
DECO_KV, then set the flag inwrangler.jsonc:wrangler.jsonc { "kv_namespaces": [{ "binding": "DECO_KV", "id": "<your namespace id>" }], "vars": { "DECO_FAST_DEPLOY": "1" } }DECO_FAST_DEPLOYaccepts"1"or"true". Don't setDECO_DEPLOYMENT_IDhere; the deploy command passes it.Call
setupTanstackFastDeploy()once in your setup module. It hands the KV binding to the admin protocol so a runtime content reload writes through to KV.src/setup.ts import { createSiteSetup } from "@decocms/blocks/setup"; import { createAdminSetup } from "@decocms/blocks-admin/setup"; import { setupTanstackFastDeploy } from "@decocms/tanstack"; import { blocks } from "../.deco/blocks.gen"; import appCss from "./styles/app.css?url"; createSiteSetup({ sections: import.meta.glob("./sections/**/*.tsx"), blocks, }); createAdminSetup({ meta: () => import("../.deco/meta.gen.json").then((m) => m.default), css: appCss, }); setupTanstackFastDeploy();Seed content at build time and pass the deployment id at deploy time. With Cloudflare Workers Builds, set the build command to your build followed by a content sync:
npx -p @decocms/blocks-cli deco-sync-blocks-to-kv --write --all --deployment-id "$WORKERS_CI_COMMIT_SHA"and the deploy command to deploy, then mark the deployment live:
npx wrangler deploy --var DECO_DEPLOYMENT_ID:"$WORKERS_CI_COMMIT_SHA"npx -p @decocms/blocks-cli deco-sync-blocks-to-kv --set-live --deployment-id "$WORKERS_CI_COMMIT_SHA"Chain each pair with
&&in the build settings. Seeding before the deploy means new code never starts without its content. Use whatever variable your CI exposes for the commit sha;WORKERS_CI_COMMIT_SHAis Cloudflare Workers Builds'.Check the delivery integration. After a deploy, publish through the project's configured workflow and verify that its content sync or runtime client updates KV. A client calling
POST /.decofilereceives"kvWritten": truewhen the write reached KV.
Without setupTanstackFastDeploy(), a runtime content reload reports success but writes nothing to KV. The change shows on the isolate that received it and disappears as others reload from KV. The same happens (with "kvWritten": false in the response) when no deployment id resolves.
Credentials for the sync CLI
deco-sync-blocks-to-kv writes through Cloudflare's KV REST API, so it runs anywhere, without a Worker binding. It reads:
| Variable | Fallback | What it is |
|---|---|---|
CF_ACCOUNT_ID | CLOUDFLARE_ACCOUNT_ID | Your Cloudflare account id. |
CF_API_TOKEN | CLOUDFLARE_API_TOKEN | A token with Workers KV Storage: Edit. |
CF_KV_NAMESPACE_ID | The DECO_KV namespace id in wrangler.jsonc | The namespace to write. |
Cloudflare Workers Builds already provides the CLOUDFLARE_* values, and the namespace id comes from your wrangler.jsonc, so no extra configuration is needed there.
Always write KV through this CLI rather than your own script. The isolates compare the snapshot's revision byte for byte with what they compute in memory; a different serialization makes them reload forever.
The sync CLI's options
| Flag | What it does |
|---|---|
--deployment-id <id> | The deployment to write. Required to write. |
--write | Apply the changes. Without it the CLI prints what it would do. |
--all | Write the full snapshot even if no block changed since --since. |
--since <ref> | Without --all, skip the write when no .deco/blocks/*.json changed since this git ref. Default HEAD~1. |
--set-live | Only record this deployment as the live one. Cheap; run it after the deploy is active. |
--blocks-dir <dir> | Where the block files are. Default .deco/blocks. |
--retain <n> | How many deployments' snapshots to keep. Default 10. The live one is never removed. |
--purge-url <origin> | After writing, call POST /_cache/purge on this origin. |
--purge-token <token> | The purge token. Defaults to the PURGE_TOKEN environment variable. |
To seed one deployment by hand (for example the very first one), deco-migrate-blocks-to-kv --deployment-id <sha> --write writes .deco/blocks/ to KV once. See CLI reference.
Roll back or turn it off
- Roll back content with code. Redeploy an older commit. It reads its own snapshot, which the sync kept (up to
--retaindeployments). - Turn Fast Deploy off. Unset
DECO_FAST_DEPLOY, set it to"0", or remove theDECO_KVbinding. The Worker serves its bundled snapshot immediately.
Code that reads content at module scope
Fast Deploy swaps the content in memory when a snapshot arrives. Code that read loadBlocks() once, at module scope, keeps the bundled content it saw at startup and never sees updates:
import { loadBlocks } from "@decocms/blocks/cms";
import { loadRedirects } from "@decocms/blocks/sdk/redirects";
// Don't: computed once from the bundled snapshot.
// const redirects = loadRedirects(loadBlocks());
// Do: read inside the request path.
export function getRedirects() {
return loadRedirects(loadBlocks());
}The framework's own redirect handling already rebuilds whenever the content revision changes. To react to content changes in your own code, subscribe with onChange from @decocms/blocks/cms (see Content and the decofile).
Bundle-stub mode (advanced)
By default an isolate holds the content twice: the bundled snapshot (the fallback) and the snapshot it loaded from KV. On sites with several megabytes of content that matters against Workers' memory limit. decoVitePlugin({ fastDeploy: true }) removes the bundled snapshot from the server bundle so only the KV copy exists.
Bundle-stub mode removes the fallback. With no bundled content, a cold start that can't read KV fails the request with a server error instead of rendering. Use it only when your pipeline always seeds the deployment's snapshot before activating it. The default, fastDeploy: "auto", stubs only when the build environment sets DECO_SEEDED_DEPLOY (set by deployment pipelines that seed KV first); Cloudflare Workers Builds and a manual wrangler deploy keep the bundled snapshot. false never stubs.
Deploying a Next.js site
On Next.js, content ships with the build. The recommended setup imports every block file through the generated .deco/blocksManifest.gen.ts, so Next bundles the JSON with your code (see Next.js App Router). A runtime content reload updates a running instance's memory; commit the content and rebuild to make it permanent. Deploy as you deploy any Next.js app.
Keeping .deco/blocks in sync with production
Studio saves edits on a working branch and merges the publish PR into the repository. If a site's content is also published somewhere else (for example, while a migrated site's old storefront is still the one editors publish to), deco-sync-blocks-bot pulls it back. It downloads GET <origin>/.decofile from the live site, writes one file per block into .deco/blocks/, and rewrites only the blocks whose content changed, so you can run it on a schedule and open a pull request with the diff.
npx -p @decocms/blocks-cli deco-sync-blocks-bot --origin https://www.example.com --dry-runBy default it never overwrites the Site block or any block holding an encrypted secret, and --fail-on-plaintext-secret stops it if an incoming block looks like it carries a credential in the clear. Its flags are in the CLI reference.
Related
- Caching: cache versioning per deploy, and purging.
- Site Editor and the v7 admin protocol: what
POST /.decofileaccepts and how it's authorized. - Configuration reference: every Fast Deploy variable in one table.
- Troubleshooting