Architecture & Concepts
Conloca is a file-based content management system that stores all content as VXJSON files in your git repository.
There is no database, no external API, and no cloud dependency. The visual editor runs alongside your Astro dev server
at the /__cms route.
This page explains how Conloca works — the content model, file format, package architecture, and data flow. Read this before diving into guides if you want to understand the system as a whole.
Overview
Conloca is a file-based CMS for Astro sites. There is no database. Content is stored as files on disk, edited
through a visual editor powered by Puck, and managed via an admin UI served at
/__cms during development.
Key design principles:
- Files as the source of truth — content lives in your repository, versioned with git
- Visual editing — drag-and-drop page builder with live preview
- Astro-native — integrates as an Astro plugin, not a separate service
- Development-mode admin — the CMS UI runs alongside your dev server
Content Model
Conloca organizes content in a hierarchy: Sites > Collections > Pages/Blocks/Data, with Locales providing language variants.
Sites
A site is the top-level organizational unit. Each site has its own collections, locales, and configuration.
A monorepo can host multiple sites (e.g., a marketing site and a blog), each with independent content. Sites are
configured in a sites.json file at the root of the content directory:
{
"sites": {
"default": {
"locales": ["en", "nl", "de", "fr"],
"defaultLocale": "en"
}
},
"globalLocales": ["en", "nl", "de", "fr"]
}
Most projects use a single site named default. Multi-site setups are useful when you need separate locale
configurations or content boundaries within one codebase.
Collections
A collection groups related content within a site. Each collection maps to a filesystem directory and contains items of the same kind.
Examples:
pages— general website pages (/, /about, /contact)posts— blog entriesproducts— product listing pages
Collections are auto-discovered from the directory structure. There is no separate collection configuration file.
Pages
A page is an individual content item with a URL pathname. Each page belongs to one collection within one site and can have content in multiple locales.
Pages are stored as files (VXJSON or MDX format) organized by locale:
content/
default/ # site
pages/ # collection
en/ # locale
home.vxjson # page content
de/
home.vxjson # German translation
Each page file contains metadata (title, description, timestamps, etags) and content (Puck component data or MDX markup).
Blocks
Blocks are reusable content fragments — think of them as “components for content” rather than “components for code.” While Puck components define the structure and rendering, blocks provide shareable content that can appear across pages.
There are two scopes:
- Global blocks — available across all sites, stored in the top-level
blocks/directory - Site-specific blocks — scoped to a single site, stored within that site’s
blocks/directory
Blocks support both VXJSON (visual editor content) and MDX (rich text) formats.
Locales
Locales provide language and region variants for content. Each site declares its supported locales and a default locale
in sites.json.
Every page and block can have content per locale. The default locale holds the primary content; other locales either provide translated versions or inherit from the default (see Content Inheritance below).
Data
Data collections hold structured key-value entries separate from page content. Examples include navigation menus, site settings, team member lists, or testimonial collections.
Data entries have a name (identifier) and typed content. When combined with data schemas (via schemasPath in the
configuration), the CMS generates structured forms for editing data entries.
VXJSON Format
VXJSON (Variant JSON) is Conloca’s file format for storing content. It is standard JSON with two guarantees:
- The
contentfield is always last in the object — this enables the indexer to read all metadata from the first 4KB of each file without parsing the full content payload - Field order is deterministic — ensures consistent etag calculation across reads and writes
A VXJSON file looks like this:
{
"id": "abc123",
"type": "puck",
"created": "2026-01-15T10:00:00Z",
"modified": "2026-01-20T14:30:00Z",
"meta": {
"title": "About Us",
"description": "Learn about our team"
},
"content": {
"puckData": {
"root": { "props": {} },
"content": [{ "type": "Heading", "props": { "text": "About Us" } }],
"zones": {}
}
}
}
Etag-Based Concurrency
Conloca uses optimistic concurrency control via etags. Each content file has a dual-format etag: metaEtag.contentEtag.
- The meta etag covers metadata fields (title, timestamps, scheduling)
- The content etag covers the content payload (Puck data or MDX)
When saving, the CMS sends the expected etag. If the file has been modified since it was loaded (etag mismatch), the
write is rejected with a STALE_WRITE error. This prevents accidental overwrites when multiple editors work on the same
content.
The dual-format design allows the system to distinguish between metadata-only changes and content changes, which is important for features like content inheritance staleness detection.
Package Architecture
Conloca is built as four packages, each with a clear responsibility:
content-api Core content engine
| (file I/O, indexing, CF Access validation, VXJSON)
v
content-api-client React Query hooks
| (useSitePages, useBlocks, mutations)
v
cms-spa Admin UI
| (dashboard, editors, media library)
v
astro-cms Astro integration
(routes, API handler, virtual modules)
content-api
The core package. Provides FileSystemContentAPI — a class that reads and writes content files, maintains an in-memory
index for fast lookups, and handles concurrency via etags. It also contains the current Cloudflare Access validation
helpers used by the Astro integration.
This package has no React dependency and can be used in any Node.js/Bun environment.
content-api-client
React hooks built on TanStack Query (React Query). Provides useSitePages,
usePageByPathname, useBlocks, useSitesConfig, useCreateContent, and other hooks that manage server state for the
CMS UI.
Handles caching, background refetching, optimistic updates, and error handling. All API communication goes through an HTTP client that calls the content API endpoints.
cms-spa
The CMS admin interface — a React single-page application. Contains the dashboard, page editor (Puck integration), block editor, data editor, media library, and settings pages.
Served as a static SPA bundle at the CMS route (default: /__cms). During development, Vite serves it with hot module
replacement.
astro-cms
The Astro integration that ties everything together. Provides the conlocaCMS() function for astro.config.mjs.
Handles:
- Injecting API routes (content CRUD, asset uploads,
/auth/user) - Serving the CMS SPA
- Content page routing (mapping URL patterns to content)
- Virtual modules for configuration injection
- File watching and HMR during development
Data Flow
Here is how a content edit flows through the system, from the editor’s action to the file on disk:
Editor makes change in Puck visual editor
|
v
cms-spa dispatches React Query mutation
|
v
content-api-client sends PUT request
to /__cms/api/content/:id
|
v
astro-cms Hono middleware receives request,
validates Cloudflare Access when configured,
routes to content-api
|
v
content-api FileSystemContentAPI:
1. Reads current file, checks etag
2. Merges changes
3. Calculates new etag
4. Writes VXJSON file atomically
5. Updates in-memory index
|
v
File written to disk with new etag,
timestamps, and attribution
|
v
Vite file watcher detects change,
sends HMR update to browser
For reading content (page loads, listings), the flow is reversed: the in-memory index provides fast lookups without scanning the filesystem on every request.
Authentication
Conloca currently supports one authentication path in the shipped code: optional Cloudflare Access JWT validation for CMS requests.
When both environment variables below are set, the CMS handler validates the incoming Cloudflare Access token before
serving /__cms and /__cms/api routes:
| Variable | Purpose |
|---|---|
CF_ACCESS_TEAM_NAME | Zero Trust team name, used to resolve the JWKS endpoint |
CF_ACCESS_AUD | Access application audience (aud) to validate the token |
When either variable is missing, validation is skipped so local development works without Cloudflare Access.
The validated user identity is then exposed to the CMS via /__cms/api/auth/user, which returns the email and sub
claim extracted from the token.
Role mapping, OAuth login flows, and API key providers are planned, but they are not part of the current branch yet. See the Cloudflare Access guide for the supported production setup.
Content Inheritance
For multi-locale sites, Conloca supports content inheritance to reduce duplication. A non-default locale page can set
sameAsDefault: true, which means it displays the default locale’s content without maintaining a separate copy.
Key behaviors:
- Inheriting pages show the default locale content automatically
- Staleness detection — when the default locale content changes, inheriting pages are flagged as “needs review” via content etag comparison
- Breaking inheritance — editors can break inheritance to customize content for a specific locale, which copies the current default content as a starting point
- Reverting — editors can revert a customized locale back to inheriting from the default
This is particularly useful for sites with many locales where most content is identical across languages, and only specific pages need translation.