<!-- wiki/overview.md - the architecture of the theme in one page: layers, data flow, outputs, commands. -->
---
title: Architecture overview
summary: How a page comes to exist in Reef, from tokens, dictionary and content collections to static HTML, and what the build actually emits.
sources:
  - astro.config.mjs
  - src/layouts/BaseLayout.astro
  - src/layouts/BaseHead.astro
  - src/pages/[...locale]/index.astro
  - src/pages/[...locale]/blog/[id].astro
  - src/content.config.ts
  - src/i18n/index.ts
  - package.json
  - tsconfig.json
updated: 2026-08-15
---

# Architecture overview

Reef is a fully static Astro 7 blog theme. There is no adapter and no server
runtime, on purpose (astro.config.mjs): the user deploys plain HTML anywhere.
The demo publication is a fictional web-studio notebook named Reef Notes; the
theme itself is called Reef, published by Aloha Pixel under the MIT licence.

The product is a PUBLICATION: posts, the authors who write them, the topics
that group them, and the pages that lead to them. Everything else (about,
contact, legal, 404) is an annex. Understanding that ordering explains most of
the decisions below.

## The layer cake

Bottom to top, each layer only knows the one below it:

1. **Tokens**: src/styles/tokens.css defines the raw palette, the semantic
   aliases and the generated utilities (see [tokens](subsystems/tokens.md)).
   global.css imports it, along with prose.css and the motion catalog.
2. **Primitives**: 36 families in src/components/ui, pure .astro, variants
   via exported tv() configs, platform-native behavior first
   (see [ui-primitives](subsystems/ui-primitives.md)).
3. **Sections**: 24 components in src/components/Sections, grouped by page
   (Home, Post, Archive, Search, Global, Legal). They compose primitives and
   carry the page-level markup.
4. **Pages**: src/pages assembles Sections inside BaseLayout, adds SEO
   metadata and JSON-LD (see [seo](subsystems/seo.md)).

There is no fifth layer. No island, no React, no animation library: nine
runtime dependencies in total.

Sideways of the cake: src/js holds pure logic (JSON-LD constructors,
pagination, text utilities), src/config holds typed site data, src/i18n holds
the bilingual layer and the two dictionaries (see [i18n](subsystems/i18n.md)),
and src/content.config.ts declares the posts, authors and topics collections
(see [content](subsystems/content.md)) whose files live under src/data/ with
identical slugs per language.

## How one page is assembled

src/pages/[...locale]/index.astro is the reference:

- `export const getStaticPaths = localePaths;` emits the page once per
  language: `/` and `/fr/`. Forgetting that line builds the page in English
  only, silently.
- It reads its copy from the dictionary (`useTranslations(locale)`), never
  from a literal, and computes its JSON-LD nodes with the constructors from
  @js/schema: Organization and WebSite. A blog home is not a sales page, so it
  declares nothing it does not show.
- It renders BaseLayout with a title and description taken from the
  dictionary, injecting the JSON-LD through `<Fragment slot="head">`.
- BaseLayout renders the single `<html>`, with `lang` and `dir` from the
  locale, BaseHead in the head, the anti-flash ThemeInit script, and a bare
  body slot.
- BaseHead writes every head tag by hand: canonical, OG, twitter, the
  hreflang set generated by `getAlternates`, a favicon generated from the
  color tokens, the three font files, the RSS discovery link and the
  ClientRouter.
- The body is Navbar, a `<main>` of five sections (Hero, TopicStrip,
  WritersStrip, StudioNote, NewsletterInvite), Footer.

A post (blog/[id].astro) multiplies `getStaticPaths` by the posts collection:
one page per post AND per language, both under the same slug, which is what
lets the language switcher land on the translation of the post being read. It
carries an Article node plus breadcrumbList.

## What the build emits

`pnpm build` (verified 2026-08-15): 55 pages. In two languages: the home, the
paginated blog, a page per post, the topic index and topic pages, the author
index and author pages, a search page, and the about, contact and legal
annexes, plus a single 404.html shared by the whole domain. Text endpoints are
robots.txt, llms.txt and a per-language rss.xml, with sitemap-index.xml from
the sitemap integration.

The 404 is rendered in the default language and carries no navbar and no
footer, because the language switcher of the navbar computes its links from
the current URL, and on a 404 that URL exists in no language.

## Invariants that shape everything

- Zero JavaScript by default. 10 of the 63 primitive .astro files carry a
  script, the rest are HTML and CSS. The site scripts are the mobile drawer,
  the theme switch, the shrinking navbar and the reader's table of contents.
- Markup speaks semantic tokens only; palette names never leave src/styles
  (docs/conventions/tailwind.md). The `.on-dark` container works only because
  the aliases live in `@theme inline`.
- `trailingSlash: "always"` makes one canonical URL shape for links,
  canonical tags, pagination and the sitemap.
- Scripts survive view transitions by re-init on astro:page-load or
  document-level delegation; `assetsInlineLimit: 0` keeps them addressable.
- The contact form ships with no `action`, on purpose. Draft posts build for
  preview but stay out of lists, RSS and llms.txt.

## Commands

From package.json: `pnpm dev`, `pnpm build`, `pnpm preview`, `pnpm check`
(astro check, must return 0/0/0), `pnpm rebrand <hex>` (repaints tokens.css
from one brand color, scripts/rebrand.mjs), `pnpm og` (regenerates the
public/og/*.png cards, scripts/og.mjs), `pnpm app` (build tuned for a
Capacitor shell, scripts/app.mjs). Selfchecks run directly with Node:
`node src/js/schema.selfcheck.ts` and `node src/js/pagination.selfcheck.ts`.
Node >= 22.18 required. Path aliases live in tsconfig.json.
