Skip to content

How Menestrel works

Menestrel separates four things most CMSs blend together: the schema, the content, the published version and the deployment. Each one lives where it is best served. Once this split is clear, the CLI, the loader and the admin almost explain themselves.

the editor you (dev)
│ publishes │ cms.config.ts + menestrel sync
▼ ▼
┌─────────────────────────────────────────────────┐
│ Menestrel │
│ versioned content + synced schema │
└──────────────┬───────────────────┬──────────────┘
│ materializes │ triggers and tracks
▼ ▼
immutable snapshot deployment targets
pushed to the CDN (Vercel, Netlify...)
│ │
│ read at build │ start the build
└─────────┬─────────┘
astro build
static site in production
zero requests to Menestrel

Collections, singletons and their fields are described in cms.config.ts, in TypeScript, with defineConfig from @menestrel/fields. That file is code: versioned, reviewed in PRs, typed. menestrel sync pushes it to the server; the CLI never applies a destructive deletion silently: it asks for confirmation on each deletion, and requires --allow-deletions in non-interactive mode.

There is no schema builder in the admin, on purpose: the structure of the site belongs to the dev, not to the UI. The editor fills fields in, they do not invent them.

Entries, their drafts and their versions live in Menestrel’s database, never in your repo. No content commits: the git history stays clean, and the end editor never meets a git concept. Nothing is locked in either: menestrel export gives back the Markdown, the JSON and the original media at any time, on every plan.

  1. The editor clicks “Publish”.

  2. Menestrel materializes a snapshot: a manifest, plus one JSON file per collection and per locale. Each file is named after its SHA-256 hash: two identical contents share one file, a file never changes, so the cache can be infinite.

  3. The snapshot is pushed to object storage and served by a CDN, at a non-guessable publication URL scoped to the project.

  4. After a debounce window (90 seconds by default, adjustable per project), Menestrel triggers the active deployment targets.

A snapshot never changes after publication. Rolling back does not “restore” data: Menestrel re-points a previous snapshot and re-triggers the targets. No migration, no loss, no suspense.

Each collection declares menestrelLoader in src/content.config.ts. During astro build, the loader resolves the snapshot through three strict modes, detailed in the loader reference: offline (MENESTREL_OFFLINE), content URL (MENESTREL_CONTENT_URL, the nominal mode in production), or discovery through a token (MENESTREL_TOKEN, for development only). The loader compares digests between builds: rebuilding on the same snapshot reloads nothing.

The build output is a static site. In production it makes zero requests to Menestrel: none when a page loads, none in the background. A traffic spike on your site is your static host’s business, never the CMS’s.

Menestrel triggers and tracks the deployment

Section titled “Menestrel triggers and tracks the deployment”

Published content only appears online after a build. Rather than leaving that step to you, Menestrel drives it: each project declares deployment targets, a Vercel, Netlify or Cloudflare deploy hook, or a GitHub repository_dispatch. On every publication, each target follows a state machine: queued, triggered, building, then live or failed. States beyond triggered come from the provider’s API, when its token is set on the target; a build still queued when a newer publication lands moves to cancelled.

The admin shows that state end to end. The editor sees “Live”, never “build” or “pipeline”.

If Menestrel goes down, here is what happens:

  • The sites stay online. They are static files on your host: nothing in the serving path of a page depends on Menestrel.
  • Builds still pass. MENESTREL_CONTENT_URL reads the snapshot from the CDN, not from the application. A redeploy during the outage succeeds.
  • Only editing waits. The realistic worst case is “editing is unavailable for a few hours”, not “the client’s site went down”.

This promise does not rest on heroic redundancy: it is structural. By the time a site needs its content, that content has already left the application.