How it is built

Boring infrastructure, arranged carefully

Postgres, a Next.js app, object storage and a forked editor. What makes it hold together is the direction the dependencies are allowed to point, and the fact that a script checks.

A vertical gradient from deep navy to pale mint.

The seam

The platform knows nothing about your content model

There is no page, post or callout anywhere in the platform packages. What lives there is everything true of any schema: the transport, the cache tags, the image URL arithmetic, the Portable Text primitives.

The example schema and the queries that match it live in the app, because that is where yours would live. Replacing the example is a matter of deleting one package's contents, not of unpicking the platform.

A warm diagonal gradient, orange over dark brown.

The packages

PackageWhat it ownsMay import
content-corePure domain: draft semantics, patches, transactions, constraints, GROQ planning, text extraction, the environment contractNothing at all
content-storePostgres: documents, transactions, revisions, GROQ execution, realtimecontent-core
content-authSupabase Auth, profiles, roles, grants, RLS identitycontent-core
content-assetsBlob bytes, metadata, transform URLscontent-core
content-apiThe Sanity-compatible HTTP surface, as pure functionsthe four above
content-nextThe Next.js adapter — the only package that may import nextcontent-api and friends
content-studioThe forked Studionothing of ours
demo-studioThe example schema, structure and templatescontent-studio
cliinit and logincontent-core

Four rules are enforced by a script rather than by convention: content-core has no dependencies at all, content-api never imports next, only content-next does, and the Studio packages may not import a server package. A new package with no entry in the rule table fails the build — so the graph cannot drift quietly.

What a request actually does

A read from the public site, end to end. Every step is in the repository and none of it is hidden behind a service.

text
browser → GET /docs/content-api/api-overview        (Next.js route, Node runtime)
        → contentQuery()                            (cached fetch, tagged with sync tags)
        → GET /api/v2026-07-26/data/query/production (this same deployment)
        → content-api router                        (pure Request → Response)
        → GROQ planner                              (predicates Postgres can evaluate)
        → Postgres                                  (RLS applies; anonymous ⇒ published only)
        → groq-js                                   (full expression, DataLoader for ->)
        → response envelope { result, ms, syncTags }
One page render, with the cache cold.

With the cache warm, the middle six lines do not happen at all: the read is served from Next’s data cache until a publish purges the tag it was stored under.

Read the specification

The repository carries a full specification and a decision log that records why each choice was made — including the ones that were reversed.

Repository tourDeployment pipeline