# ADR-031: One domain, and the URL says which surface you are on

| | |
| --- | --- |
| **Status** | Accepted |
| **Date** | 2026-08-03 |
| **Author** | bodurkalukasz |

## Context

The ecosystem is not one repository. The core carries the method; a stack repository carries
the tooling for a technology, and [ADR-025](ADR-025-the-standard-is-living-latest-is-the-target.md)
says a repository adopts the latest of each independently. Each has a landing page and a
documentation site, generated by the same generator from its own markdown.

That is four surfaces, and until now nothing decided where they live. The generator carried
a `base_path` for exactly this and no site ever set it, so the question stayed open in a way
that only looked settled: the switcher in the header hardcoded "here" on the core entry and
sent the stack entry off to GitHub, which is right on the core's pages and wrong on every
page the stack would ever build.

The pressure is not aesthetic. A reader who lands on a stack page and wants the method it
implements has to get there, and a search engine has to be told these are one product rather
than several.

## Options considered

- **A - A domain per repository** (`repositorystandards.com`, `node.repositorystandards.com`).
  Rejected: it splits the authority a single domain accumulates, doubles the certificate and
  DNS work per stack, and makes every cross-link an off-site link - including the ones that
  are conceptually one step, like a stack page pointing at the rule it implements.
- **B - A path per repository, docs nested under it** (`/node/`, `/node/docs/`). Rejected:
  it puts the surface first and the audience second, so `/docs/` stops being one place. A
  reader looking for documentation has to already know which stack they want before they can
  find any.
- **C - Surface first, stack second.** Chosen: `/` and `/docs/` are the core's, `/node/` and
  `/docs/node/` are the stack's. Documentation is one tree with a stack living inside it,
  which is also how the reader's question is shaped - *the docs, for Node*.

## Decision

**One domain, `repositorystandards.com`, with the surface first in the path.**

| URL | What is there |
|---|---|
| `/` | the core's landing page |
| `/docs/` | the core's documentation |
| `/node/` | the Node stack's landing page |
| `/docs/node/` | the Node stack's documentation |

Each repository builds its own site and knows only two things about where it will be served:
`site_root` (where its landing sits) and `base_path` (where its docs sit). Nothing else in the
generator is layout-aware, and a second stack is two config lines rather than a change here.

**Every internal link is written root-absolute against `base_path`.** This is not a style
preference. A relative link is resolved against whatever the browser believes the base to be,
so a single visit to `/docs` without the trailing slash - which is what people type, and what
a link in a chat message usually carries - re-based every link on the page one level up and
broke the sidebar along with them. Under this layout that stops being a local-server curiosity:
`/docs/node` without the slash would resolve a stack's links into the core's tree, which is
worse than a 404 because some of them would work.

**The ecosystem switcher decides "here" by comparing against the site's own base**, not by
being told. The same generator builds every site in the ecosystem, so any hardcoded answer is
correct on exactly one of them.

## Consequences

- Cross-surface links are same-origin: a stack page can point at a core rule and stay on the
  site. The switcher becomes navigation rather than an exit.
- One certificate, one analytics property, one robots and sitemap story, and search-engine
  authority accrues to one domain instead of being divided per stack.
- Adding a stack costs a path and two config lines. It does not cost DNS.
- **The cost:** the surfaces are joined at deploy rather than at build. Nothing in either
  repository can verify the whole tree, so a link from one surface into another is checked
  only against the layout this record describes - which makes this file, and not a script,
  the thing that keeps them consistent.
- The redirect from `/docs` to `/docs/` is now load-bearing and belongs to the host. It was
  already needed; under this layout its absence is a correctness bug rather than an annoyance.

## Confirmation

`site-check` fails any generated page carrying a relative internal link, so the root-absolute
rule is mechanical inside a repository. The switcher's "here" is derived rather than written,
so it cannot disagree with the site it is on.

Publishing belongs to this repository, and it needs no second one to do it. **A project site
with a custom domain is served at the domain root**, not under its repository name - the
`/<repo>/` prefix applies only to the default `<org>.github.io` address. So the constraint
that seemed to force a separate assembling repository does not exist: one repository can
publish every path in the table above.

The workflow builds this repository's surfaces, then walks `stacks.json` - the registry this
repository already publishes - and builds each registered stack into its own prefix. One
list, not two, so a stack cannot be registered and unpublished by disagreement between
files. A stack whose repository is not reachable yet is **warned about, not skipped
silently**: a missing surface nobody mentions reads as a surface that was published.

Nothing is committed. The site is uploaded as an artifact, so no repository in the ecosystem
carries build output, and no repository needs write access to another.

What is **not** mechanical, and is worth stating rather than implying: nothing checks that
`/docs/node/` actually serves the stack's build, because no repository can see the deployed
tree. That is verified by opening it after a deploy.

## Revisit when

A stack needs its own brand or its own domain for a reason that is not technical - a
partnership, a separate product identity. That is a positioning change and it reopens this.
Wanting a shorter URL does not.

## Related

- [ADR-025](ADR-025-the-standard-is-living-latest-is-the-target.md) - each repository is
  adopted at its own latest, which is why they are separate builds joined at deploy.
- [ADR-014](ADR-014-one-authored-tree.md) - the same instinct one level down:
  one authored tree, and the path says which zone you are in.
