How Antora assembles a site
This explanation covers the model underneath the commands you ran in the tutorial: what Antora actually does with your repositories, why the design splits content from assembly, and where the model's assumptions stop holding. Nothing here is required to operate a site; it's the page for the person who wants to know what the architecture is for.
The problem Antora is shaped by
General-purpose static site generators share an assumption: the site is a directory. Point the tool at a folder of files, get HTML out. That assumption quietly fails for product documentation, which is rarely one directory. It's the server docs in one repository and the client docs in another; it's version 1.4 that must stay published for the customers still running it; it's a snippet that twelve pages share and one team maintains.
You can fight a directory-shaped tool into doing this (submodules, copy
scripts, a v1/ folder that drifts from reality). Antora's bet is different:
the unit of content is a git reference, not a directory, and the site is
a function over a set of them.
From playbook to site, in four stages
A build is a pipeline with a declarative recipe, the playbook, at the top:
- Aggregation. Antora clones or fetches every repository in
content.sourcesinto a local cache and reads the configured branches and tags directly from git's object database, without checking anything out. A working tree never exists; a branch is just a tree of blobs to read. This is why an uncommitted file is invisible to a normal build, and it's what makes "readmain,v2.0, andv1.0simultaneously" cheap. - Classification. Files from each branch are sorted into a content
catalog under coordinates, not paths: component and version (from that
branch's
antora.yml), module (from the directory undermodules/), and family (page,partial,example,image,attachment, from the directory the file sits in). The rigid standard layout isn't bureaucracy; it's the parser. - Conversion. Pages are converted from AsciiDoc, and every
xref:andinclude::is resolved as a lookup in the catalog, not on disk. A reference either resolves to a real resource, possibly in another component or version, or it's logged (and, withruntime.log.failure_level: error, fails the build). Link integrity is a property of the model, not a crawler bolted on afterward. - Generation. The resolved pages are poured into the UI bundle's
templates, navigation trees are built from the
nav.adocfiles, and the version selector falls out of the catalog's coordinates. The output is inert HTML; nothing of the pipeline survives into production.
Why versions are branches
The catalog's version coordinate comes from antora.yml as it exists on
each branch, and that choice carries most of the workflow's value:
- Freezing docs is free. A release branch exists the moment you cut it; its docs are frozen with it. No copying, no "snapshot the wiki" ritual.
- Backports are cherry-picks. The operation teams already trust for code applies to docs unchanged, review included.
- History stays honest. The 1.0 docs are whatever the
v1.0branch says, with every change attributed and revertible. A folder-per-version scheme preserves none of that once files are copied.
The subtler consequence: because version identity lives in the content repository rather than the playbook, the product team owns what "2.0" means, and the docs platform merely decides which versions to publish. That boundary — content repositories own truth, the playbook owns assembly — is the whole political design of Antora, and it's why a docs platform team can serve many product teams without being a bottleneck for any of them.
Why links are IDs
A relative file path encodes an accident: where two files happen to sit
right now, in one version. A resource ID (1.0@widget:admin:backup.adoc)
encodes an intent: this page, in that component, at a version I either
pin or inherit from the reader. Intent survives reorganization; accidents
don't.
IDs are also what make cross-version and cross-component references sane. An unqualified xref follows the reader's current version, so the 1.0 docs link within 1.0 and the 2.0 docs link within 2.0 — from the same source line, cherry-picked between branches without edits.
Where the model leaks
Honest limits, so you can decide with your eyes open:
- The layout is non-negotiable. Content must live under
modules/<name>/<family>/. Migrating an existing docs tree means moving files into that shape first; there is no "just point it at my folder." - Builds are whole-site. Antora rebuilds everything the playbook names on every run. Sites in the thousands of pages build in minutes, not seconds: fine for CI cadence, occasionally irritating for local preview during heavy writing sessions.
- The UI bundle is a second artifact. Decoupling the frontend from content builds is the right call, but customizing the default UI means owning a fork of a Gulp-era frontend project. Budget for it once, then leave it alone.
- Attributes are a global namespace. AsciiDoc attributes make single-sourcing possible and, past a point, make pages hard to read without building them. Keep a short, documented attribute vocabulary; resist the urge to parameterize prose that was fine as prose.
- It's docs-shaped, deliberately. Antora generates documentation sites. Marketing pages, blogs, and app-like frontends belong in other tools, and the teams that accept that early are happier than the ones that fight it.
What to take away
The pipeline view explains the operational rules of thumb elsewhere in this
documentation: content must be committed because aggregation reads git, not
your working tree; antora.yml is read per branch because that's where
version identity lives; xrefs are cheap to validate because resolution is a
catalog lookup; and the playbook repository stays tiny because assembly is
the only thing it owns.
If that model matches your documentation problem (several sources, several versions, one site), very little else in this space is built for it. If it doesn't, a simpler directory-shaped tool will serve you better, and the overview says so without hard feelings.