Ir para o conteúdo
decodecodeveloper docs
Storefront → Blocks → Referência de engenharia

How hosted releases stay current

Esta página ainda não foi traduzida para o português — o conteúdo abaixo está em inglês.

With the hosted Deco CMS, createCMS with a site and token wraps your content in remoteLoader, which keeps each server on the latest release without a deploy. Publishing without a deploy covers what that means for your site. This page shows how it works inside: how a server checks for a release, swaps it in, and stays fast while it does.

The delivery API serves prepared assets from storage/CDN; even background checks, cold reads and errors never call GitHub.

The rule that matters: a production request never waits for the network. load(), the loader's read method, returns from memory, immediately, every time. Staying current happens beside requests, not in front of them.

Request path · synchronous, never touches the network

Request
client.list or client.resolve
load() from memory
Newest fetched release, or the fallback loader's content
Content resolves
Same revision for the whole request

Background path · on first use, then about once a minute, when idle

Ask for the hash
A few bytes from the Deco API
Compare
Same as memory: done. Same as the fallback: use it, no download.
Fetch, verify, swap
Whole revision at once. Any error: keep what's there.
  1. Serve from memory. load() returns the newest release this process has fetched, or, if it hasn't fetched one yet, the fallback's content. A freshly started server (a cold start) serves the content the build shipped with on its very first request.
  2. Ask for the hash. In the background, on first use and then every interval, update() fetches a small channel manifest containing the current revision and a monotonically increasing promotion generation from the delivery API. That's a few bytes. The interval is one minute by default; set it with interval or the DECO_CONTENT_INTERVAL environment variable, never under 60 000 ms. Each check is scheduled one interval, plus or minus up to 10 s, after the previous one (60 s ± 10 s by default; the random part is called jitter), so servers that started together don't check together. A check that's due waits for an idle moment, a gap when the process has nothing else to do:
    • requestIdleCallback where it exists (browsers, React Native);
    • otherwise scheduler.postTask({ priority: "background" });
    • otherwise setTimeout;
    • on Node, an unref'd timer (one that doesn't keep the process alive) plus setImmediate, so it never keeps the process running;
    • on Cloudflare Workers, which run no timers between requests, after the response inside ctx.waitUntil (the Worker's way to finish work after a response is sent), automatically. Updating is scheduled outside the response path, and a request never waits for it; parsing and refresh work still share CPU and memory with rendering. There's no push signal: servers ask, the API never calls them.
  3. Compare. Ignore manifests older than the newest observed generation. If the hash equals what's in memory, no content download is needed, but adopt the generation. If it equals the fallback's revision, the content the build shipped with is current: the loader uses it and never downloads it. This is the common case right after a deploy: the content module's revision is the CLI's copy of the same hash. A loader you write with its own revision scheme is downloaded once.
  4. Fetch and swap. Otherwise it fetches that immutable revision asset, checks that the content hashes to it, and swaps it in whole. A release or draft over 64 MB is refused while it downloads, so one oversized publish can't exhaust a server's memory. Before swapping, confirm this is still the newest observed generation; a slower earlier refresh must not undo a publish or rollback. A higher generation can select an older revision. Whether that content fits this build was settled before it merged, not here (see Backward compatibility). Requests in flight keep the revision they started with.
  5. Fail quietly. Any network error, or a snapshot that doesn't parse, leaves memory as it was, so a server that loses the API keeps serving the newest release it has. Nothing reaches a request.

So different servers can briefly serve different releases, by design. A published change reaches each server on its next check plus the manifest cache delay (or after a successful immediate check on the one server where you call cms.update()), and each server sends one tiny request per interval, however much traffic it serves. (This is called eventual consistency.)

Drafts fetch only a versioned overlay and changed block blobs, waiting for those assets and caching them afterwards. The client captures whatever production content is local at first use and layers replacements and deletion tombstones over it. There is no base-revision field or matching-release fetch. Later clients can inherit newer local production; in-flight clients keep their original pair. Release checks don't depend on whether a server renders drafts: a draft client is a production client reading a draft pointer, so it counts as use like any other, and the check never runs in front of it. A failed overlay makes draft calls return an error. See Draft overlays for fast previews.

One instance per process

A cache and a poller only help if there's one of each. createCMS and remoteLoader store their instances on globalThis, under keys made with Symbol.for("decocms.blocks…"). Symbol.for returns the same symbol for the same string anywhere in the process, so two copies of the package, from two bundles or from a dev reload (hot module replacement, HMR), find each other's instances. Without it, each copy would keep its own cache and run its own poller, doubling the memory and the checks. The key is derived from the configuration (the site ID, the token and the content's identity: for the content module, the .deco folder it was generated from; for a loader you write, the loader object. Never the revision, so a hot reload that hands in new content keeps the same instance), so several sites in one app get separate instances. A second call with the same key but different options, such as another interval, keeps the first instance and logs a warning that names the conflicting options, so there is still only one cache and one poller and the mismatch is visible. Each call gets its own handle on the instance, holding the block map it passed, so a second bundle in the process (Next.js's proxy.ts) shares the cache and poller without replacing the app's block map. resetForTests() clears every stored instance.

A loader you write that has update() is checked the same way: the CMS calls its update() on this idle schedule and interval. To connect a site, see Connect your site; for the interval, Loaders; for how telemetry behaves with several instances, Several CMS instances; for what a release is, How a commit becomes a release.