Site / Creating a site

Building nodes: options or builder

The options bag above covers the common case. To annotate in steps, or to share an annotation bundle across nodes, build incrementally — the options are sugar over this builder, one implementation:

ts
const Person = Site.node(personRouter, AppRoute.Person)
  .title(({ personId }) => `Person ${personId} | Routing`)
  .section('People')
  .node

Methods — not pipeable fragments — because each stage of a pipe is a separate generic call and cannot infer the route, while a method already knows its node's. Named bundles compose through .pipe (or free pipe) with no annotation:

ts
const inPeopleSection = <R extends { readonly _tag: string }>(b: NodeBuilder<R>) =>
  b.section('People').shortcut(['G', 'P'])

const People = Site.node(peopleRouter, AppRoute.People).pipe(inPeopleSection).node

Read .node at any stage: every step freezes, so intermediates are usable nodes too (targets, hrefs), never drafts. The full annotation set is title, section, landing (the section's nav address, as this node's params), shortcut (the keys that go to its landing; Site.shortcuts binds them), history (below), surface (below), layout, and view (below).

Two deliberate absences, both load-bearing. The tag always derives from the route case — no custom names — because one route case is one node, which is what keeps chain inspection unambiguous. And there is no SitePlan object, no Site.prefetch, no prepare: preparation stays caller-composed, as the data-fetching sections show.