Publishing without a deploy
With the hosted Deco CMS, production commits prepare immutable releases; promotion and rollback reach running servers on their next background check.
A price on the homepage is wrong in the middle of a sale. Without the hosted Deco CMS, publishing is committing and the fix waits for a deploy (see Deployment). With it, there's no rebuild: the Deco API prepares the commit as an immutable release, and the servers already running your app pick it up after promotion on their next background check.
This page shows how a commit becomes a release, how servers pick it up, and what keeps running code compatible with new content.
How a commit becomes a release
Your production branch (usually main) moves: someone pushes or merges a commit in .deco/blocks, or an editor publishes a draft in the site editor. Then:
- The control plane prepares the commit as a complete JSON snapshot in object storage. Only after the asset is ready does it promote the production channel manifest. A release never changes; its revision identifies it. Production servers read storage/CDN assets and never GitHub.
- Each server checks for a new release about once a minute, at an idle moment, in the background. A request never waits for it: requests always read from memory.
- The server swaps the release in whole and serves it from its next client on (the object
cms.forRelease()returns). Requests already in flight keep the revision they started with (see One revision per response).
A publish doesn't check content against your schema: deco check in CI does that (see CI on site editor commits), and at request time content that doesn't fit fails only its own block, while two routes for one URL resolve to the earlier one.
A publish needs no application rebuild. Propagation depends on preparation time, the manifest cache lifetime, each server's check interval and rendered-page caching; it is not an immediate global switch. Different servers can briefly serve different releases, by design.
On Cloudflare Workers, which run no timers between requests, the SDK runs a due check after the response, inside ctx.waitUntil (ctx is the Worker's execution context; waitUntil lets work finish after the response is sent). You don't call anything.
Calling cms.update(), for example from a webhook or an admin "refresh now" button, checks at once, but only on the server or isolate that receives the call; every other one picks the release up on its next check. It never throws.
The request path, the idle scheduling on each runtime and the swap are described step by step under the hood.
Fast content rollback
Choose a retained release compatible with the deployed code. Rollback advances the production channel's generation while pointing it to that older snapshot. It needs no rebuild, code deploy or GitHub read. Servers follow it through the same refresh path as a publish, and rendered-page caches must also observe the promotion.
Rollback pauses automatic promotion until explicitly resumed, so delayed jobs and new commits cannot silently undo it. It does not change the repository: a Git revert can reconcile that later. Code rollback is a separate operation; select compatible content for the older build. Retention, publication ordering and recovery are described in Production delivery and rollback.
Check interval
// Check for new releases every 2 minutes instead of every minute
createCMS({ blocks, content, site: process.env.DECO_SITE, token: process.env.DECO_SITE_TOKEN, interval: 120_000 });interval is the core createCMS option that paces every content source that can change (see Loaders); with a connected site, that source is the Deco API, so it's how often each server checks for a new release. It defaults to the DECO_CONTENT_INTERVAL environment variable, or 60 000 ms (one minute), and lower values are raised to one minute, with a warning. Each check runs one interval, plus or minus up to 10 seconds, after the previous one, so servers that started together don't check together. A check fetches only the current revision hash, a few bytes, however much traffic the server serves.
Fallback
Until a server has fetched its first release (a new server, or one that has never reached the Deco API), it serves the content module your build shipped with: the content this deploy was tested against, so it's always there and always compatible with the code. If the Deco API becomes unreachable later, the server keeps serving the newest release it already has. Any network error, or a snapshot that doesn't parse, leaves memory as it was; nothing reaches a request.
The CMS always switches between whole revisions, never mixing entries from two of them, because a mix can break references or bring back deleted entries.
Same revision, no download
deco content and the Deco API compute a revision the same way, as a hash of the whole content map, so two copies of the same content get the same revision wherever they're computed. When a server's check finds that the current release has the revision of the content module it was built with, which is the common case right after a deploy, it uses the bundled copy and never downloads it.
Keep running code compatible
Publishing without a deploy means the code that's already running renders the new content, and nothing at runtime checks that the two fit. On top of the backward-compatibility rules every site follows, this adds one rule: deploy the code first, then publish content that uses it.
Content must only use block types and fields the deployed code has. The site editor reads the schema committed on the branch it edits, falling back to your default branch when that branch has none (see What the site editor reads from the schema), so keep that schema in step with what's deployed. An entry that uses a type the deployed code lacks fails that block with UNKNOWN_BLOCK (see Errors) until the code ships.
Before rolling code back, select a retained content revision compatible with the older build. Newer content may use types or fields the older code lacks. Content rollback and code rollback are independent; coordinate them explicitly.
Large content on Workers
With site and token (your site ID and token), the bundled content module stays in memory as the fallback beside the live release, so a published site holds two copies (a Worker isolate has 128 MB), and the bundle counts toward the script size limit. That only matters at several megabytes of JSON; for sites that large, pass a loader that reads Workers KV as content instead, which keeps the fallback in KV; it's a few lines of your own code (see Example: Workers KV). Your deploy must write the content to that key before the new Worker takes traffic (for example with wrangler kv key put); if the key is missing, a new isolate's first reads fail until its first release check.
Troubleshooting
To see which release a server is serving, log await client.revision() from a fresh client (see createCMS).
import { createCMS, remoteLoader } from "@decocms/blocks";
import blocks from "./.deco";
import content from "./.deco/blocks.gen";
const loader = remoteLoader(content, { site: process.env.DECO_SITE, token: process.env.DECO_SITE_TOKEN });
export const cms = createCMS({ blocks, content: loader });
// in a request handler or a debug route:
console.log((await loader.load()).revision);| Symptom | What to check |
|---|---|
| Stale or fallback content | Compare the revision your server serves with the revision of the latest release. A server that hasn't fetched a release yet reports its fallback's revision, which matches when the fallback is the content module. If they differ, the server hasn't checked yet: allow preparation time, the manifest cache lifetime and the configured check interval, or call cms.update(). An idle Worker checks after a subsequent response. Check that both site and token are set in production. |
UNKNOWN_BLOCK right after publishing | The content uses a block type the deployed code doesn't have, usually because the content went out before the code that defines the type, or the code was rolled back to a version without it. Deploy the code, or revert the content commit. See Keep running code compatible. |
Warning that interval was raised | The minimum is 60 000 ms (one minute); lower values, from interval or DECO_CONTENT_INTERVAL, are raised to it. |