The compiled graph
A graph of nodes connected by @id pointers raises an obvious question: when a page needs a Place and all of its reviews, who follows those pointers, and when? Most systems follow them at request time — a query, a join, a cache layer to make the join bearable. CTXR follows them once, at write time, and never again.
Write-time resolution
When an editor approves content, the compiler resolves every @id stub into the full referenced node. The draft holds pointers; the live record holds the resolved result.
Draft: { "itemReviewed": {"@id": "ctxr:a1b2c3"} }
│ approve → compile
▼
Live: { "itemReviewed": {"@type": "Place", "name": "Fikkie", "geo": {…}} }
The result is a compiled graph: a self-contained document that already holds everything a page needs to render. A Place page has its reviews inlined. A route page has its stops, each with their coordinates, already resolved. A template opens one JSON file and is done — zero runtime lookups, no joins, no second query.
This is the key move. The cost of resolving references is paid once, when the editor clicks approve — not on every page view by every visitor.
Two kinds of reference
Not all references behave the same way, and the difference is worth understanding because it shapes how the graph scales.
Curatorial references point downward: a parent that contains a child. A page that curates a list of Places owns those Places in its compiled output — they’re inlined directly. This is the graph domain: resolved at compile time, living on disk.
Relational references point upward: a child that names its parent. A Review names the Place it reviews. There can be thousands of these, so they aren’t all inlined. Instead they’re answered on demand and cached. This is the query domain: computed at request time, cached, and thrown away when the underlying data changes.
The split matters because it keeps compiled documents bounded. A wildly popular Place with a thousand reviews doesn’t produce a thousand-review live record. The compiler inlines up to a fixed number of related items and leaves the rest to the query layer — while still computing accurate totals and aggregate ratings across all of them.
Depth keeps the graph acyclic
References have a direction, and that direction is governed by depth. Every type sits at a level: a page or a Place is shallow (depth 0); a Review, a list item or a menu entry sits a level down (depth 1); an image or a person sits deeper still (depth 2).
The rule is simple: a node may only reference nodes below its own depth. A page can contain a Place, a Place can carry an image — but an image can’t point back up at the page. That single constraint guarantees the graph has no cycles, which is what makes write-time compilation terminate: the compiler can resolve and cascade without ever chasing its own tail. The rule is enforced in more than one place — when content is created, when it’s saved, and when it’s compiled — so a malformed reference is caught rather than quietly corrupting the graph.
The cascade
A compiled document is a snapshot, so when source data changes, the snapshots that include it must be rebuilt. The compiler does this automatically through a cascade.
When you approve a Place, every page that curates that Place gets recompiled with the new data. When you approve a Review, the Place it reviews gets its aggregate rating recalculated. The editor clicks approve once; the platform updates everything that depended on it.
The cascade follows a topology index — a persistent map of which nodes reference which, and whether each link is curatorial or relational. It works bottom-up: the changed node first, then its curatorial parents, then theirs, and it stops as soon as it reaches nodes that don’t reference the changed content. Recompiling a node touches only its local ancestors, not the whole space — so the work scales with how many places a node actually appears in, not with the total size of the site.
A small worked example. Imagine a home page and a walking route that both feature the same Place, which in turn has reviews:
WebPage (home) ──curates──▶ Place (fikkie) ◀──relates── Review
TouristTrip (route) ──curates──▶ ListItem ──relates──▶ Place (fikkie)
Approving an edit to fikkie recompiles the home page and the route, because both curate it — but it does not recompile the reviews, which merely point up at it. Approving a new review, by contrast, recomputes fikkie’s aggregate rating and cascades from there. The topology index is what lets the platform know, in each case, exactly which documents to rebuild and which to leave alone.
What you get
The compiled graph is what makes CTXR fast without bolt-on caching layers, CDNs or optimization tricks. The graph is always complete, always pre-resolved, always ready to serve. The system reaches consistency at the level of the filesystem: after a cascade settles, every live document on disk is correct.
The trade — and it’s a deliberate one — is that writes do more work than reads. That’s exactly the bargain we want, and the next page explains why.