Nodes & Relations
Nodes & types looked at a node on its own. This page is about the lines between nodes — how one node relates to another, what kinds of relation there are, and how the resulting sets are ordered and paginated. Rendering those sets into markup is a separate concern, handled by Template helpers; here we deal with what the relations are.
Three kinds of relation
Use of semantics gave every reference a kind — compose, aggregate, or link. Those kinds are exactly what produce the three kinds of set a page draws on. The difference isn’t cosmetic: it decides where the set lives, when it’s computed, and what happens when a member is unpublished.
curated compose hand-picked, hand-ordered stored on the parent
relational aggregate derived from who points back read from the topology index
queried query matched on type / property computed fresh from the indexes
Curated (compose) — an editor explicitly includes specific nodes and arranges them. A guide’s mainEntity.itemListElement is a curated list of stops. The set is the parent’s data, resolved into the parent’s compiled page.
Relational (aggregate) — assembled from the reverse direction. “All reviews about this place” isn’t stored on the place; the reviews name the place, and the set is the inverse lookup — its direct inbound, baked onto the place as a bounded array plus a derived summary.
Queried (query) — assembled by matching, across the hierarchy. “All places in this space” is a type query; “every node with this OUP identifier” is an identifier query. Nothing is stored; the set is computed from the indexes at render time, and never baked — the compiled-versus-queried line is exactly this boundary.
Order and pagination
A relation is a set, and a set needs an order and a size limit.
Order. Curated sets carry their order explicitly: each member holds a position, and reordering in the editor rewrites those positions. Relational and queried sets have no editorial order, so they sort by a sensible default — most recent first, nearest first — chosen per context.
Pagination. A set can be large, so compiled records never hold all of it. The compiler bakes a bounded first page and marks whether more exists:
{
"review": [ /* first page, bounded */ ],
"_reviewHasMore": true,
"_reviewCursor": "…" // opaque cursor for the next page
}
_hasMore / _cursor are an architectural convention, not a per-list option: any set that can outgrow its inline cap exposes them, and a second page is fetched live (a query) rather than baked. The first page is instant from disk; the long tail is paid for only if someone asks for it.
Unpublish behaviour differs
This is the practical reason the distinction matters:
| Kind | Remove a member by… | Effect |
|---|---|---|
| Curated (compose) | editing the parent | The parent recompiles without it |
| Relational (aggregate) | unpublishing the member | The topology index updates automatically; sets everywhere drop it |
| Queried (query) | unpublishing the member | It simply stops matching — no edit anywhere needed |
Unpublishing one popular place quietly removes it from every relational and queried set it appeared in, without anyone touching those pages. Curated sets are the only ones that need an explicit edit — which is exactly right, because a curated set is an editorial decision, not a derived fact.
Derived values
When the compiler resolves a relation, it also measures it. Values you’d otherwise compute by hand are derived and kept accurate automatically:
{
"@type": "TouristTrip",
"name": "Stadswandeling",
"_stopCount": 7, // counted across all stops
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6", "reviewCount": 128 // computed across all reviews
}
}
stopCount, reviewCount, aggregateRating and friends are calculated over the full set of related nodes — even the ones the compiler didn’t inline. A place with a thousand reviews shows an accurate reviewCount of 1000 while its live record carries only the first page inline. The number is always true; the payload stays bounded.
Projection
A set rarely needs every field of every member. Projection is the compiler taking only the properties a given context needs. A map sidebar projects places down to name and geo; a review strip projects them to name and image. The same node appears in several sets, each carrying a different slice of it.
Projection is what keeps compiled documents small without you writing per-set field selectors. (The API exposes the same idea explicitly — the fields parameter on a query projects results down to the keys you ask for.)
How the system tracks it all
Behind every relational and queried set is the topology index — a derived map of which node references which, and of what kind each link is. It powers the reverse lookups and tells the compiler which parents to recompile when a member changes. It’s rebuildable from the data itself: delete it and the next compile reconstructs it exactly. You never maintain it — you rely on the three kinds behaving the way this page describes, and you render them with the list helpers.