Ir para o conteúdo
decodecodeveloper docs
Storefront → Blocks → Content and data

Content and loaders

deco content turns your saved blocks into a module your bundler packs. createCMS reads it, or a loader you write fetches content from somewhere else.

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

Your saved blocks are JSON files in .deco/blocks. If your app runs on Cloudflare Workers, or any host without a filesystem, there's nothing to read them from at runtime. deco content turns those files into a module your bundler packs into the build, like any other import. When the build isn't the right place for your content, a loader you write fetches it instead.

This page shows how saved blocks reach your running app: the content module, the cms object that reads it, and how to write a loader.

The content module

The deco CLI, which comes with @decocms/blocks, turns .deco/blocks into a module your app imports. So the SDK never touches the filesystem and runs anywhere: Node, Cloudflare Workers, Deno, Bun or a browser.

Proposed CLI example — unreleased
npx @decocms/blocks content

It writes .deco/blocks.gen.ts: one JSON import per file, exported together, so your bundler packs your content into the build:

.deco/blocks.gen.ts (excerpt)
// Generated by deco content; don't edit.
import HomePage from "./blocks/HomePage.json" with { type: "json" };
import PromoBanner from "./blocks/PromoBanner.json" with { type: "json" };
// … one import per file, exported together
  • Gitignore it. The generated file belongs in .gitignore, like TanStack Router's routeTree.gen.ts. Run the command before your dev server and your build, after deco schema (see Run it before dev and build).
  • Editing a file needs no rerun. It reloads through your framework's hot module replacement (HMR).
  • Adding or removing a file does. Rerun npx @decocms/blocks content, or leave npx @decocms/blocks content --watch running beside your dev server.
  • It reads only .deco/blocks. It bundles the JSON files there and never reads your block map.

Like every command, it finds .deco/ by walking up from the current folder, or takes --root. Every option is in the CLI reference.

Create the CMS

Pass the content module to createCMS, next to your block map. Create it once, at module scope:

cms.ts
import { createCMS } from "@decocms/blocks";
import blocks from "./.deco";
import content from "./.deco/blocks.gen";
 
export const cms = createCMS({ blocks, content });

Then ask it for a client per request. cms.forRelease() returns a client that reads the content everyone sees, the release:

const client = cms.forRelease();
const [page, error] = await client.resolve("HomePage");

One client per request keeps a whole response on one content revision (see One revision per response). For a draft, use cms.forDraft(pointer) instead.

createCMS also takes a telemetry option, which says where measurements from your app go; see Telemetry.

Write a loader

The content module covers almost every site: the content of the commit you deployed ships in the build. Write a loader when your content lives somewhere else, such as your own storage, or when you want to serve drafts.

A loader is any object with a load() method that returns a snapshot, { revision, blocks }. load() with no argument is the release; load(pointer) is the draft a draft pointer names. If the content can change while the process runs, add update():

cms.ts
import { createCMS, type Loader } from "@decocms/blocks";
import blocks from "./.deco";
import fallback from "./.deco/blocks.gen";
 
const myLoader: Loader = {
  async load(pointer) {
    if (!pointer) return fallback;                         // the release: what this build shipped
    return fetchDraftFromMyStorage(pointer);               // your code: validate the pointer first
  },
};
 
export const cms = createCMS({ blocks, content: myLoader });
  • Treat the pointer as untrusted. It comes from request input. Allow-list the hosts your loader may fetch from, so a crafted pointer can't make your server fetch arbitrary URLs, and require a credential you can verify, such as a signed token, before serving unpublished content.
  • update() never blocks a request. A loader with update() is checked every interval (default 60 000 ms, never less), at the next idle moment. cms.update() checks at once, for a webhook or a "refresh now" button.
  • A failed draft is an error. A content source with no drafts, like the content module, ignores the pointer. Otherwise, if load(pointer) fails, or the pointer doesn't parse, every resolve and list on that client returns [null, error] with LOADER_FAILED. The CMS never silently substitutes published content after a failed draft load; your app decides what to show. Hosted draft loading deliberately composes a valid overlay with a captured local production snapshot, including explicit deletion tombstones (see Draft overlays).

The exact Loader interface, interval, the loaders the framework ships, and a loader for Workers KV you can copy are in Loaders.

One instance per process

createCMS shares one instance for the same configuration, even if your bundle loads the package twice, and each call resolves with its own block map; see One instance per process.