How this site is built
The specific site and the general system at once — the instance is the example; the pattern is what you take away.
Read this two ways at once. On the surface it is a description of one small website — its files, its routes, the host it sits on. Underneath it is a worked example of a general pattern, and the pattern is the part worth keeping. So each section names the specific thing and the transferable rule it stands for. Strip out the name and the particular work, and what remains should still be buildable by anyone.
The terminology is now separated more precisely. Public Interface defines the durable contract and its trust boundary. The architecture record reports what this production instance actually implements and how it performs. This note explains the engineering reasoning between them.
The thing to resist is the obvious framing. This is not a brochure, and it is not even mainly “a publishing system.”
It is a flexible public operating surface: built to evolve quickly under agent management, while keeping its source legible, its outputs derived, its claims auditable, and its maintenance surface small.
Everything below is downstream of that one sentence. The stack is not a set of taste preferences; it is a set of answers to the question how do you make a surface that can change fast without becoming fragile or dishonest.
§1 What this is
A single durable surface that several different jobs run through. Concretely, for this site, the jobs are these — and they are roughly the jobs any person, company, or project eventually needs a public surface to do:
- Explain
- Let a human or an agent understand who this is, what the work is, and how to engage.
- Publish
- Hold essays, notes, technical memos, and operating artifacts — durably, at stable URLs.
- Evidence
- Show work, decisions, and change over time, with receipts. The Done log is this function.
- Coordinate
- Make contact, collaboration, and context-transfer easy, for people and for their tools.
- Represent
- Give humans and language models a current, structured model of the person and the work.
- Adapt
- Let the surface change quickly as the work changes, without each change risking the whole.
- Operate
- Support agent-managed updates, QC passes, derived artifacts, and future workflows.
The site is the current expression of that list, not the final one. New functions get added the same way everything else did — as content and small derivations, not as a rebuild.
§2 Functional objectives
Before any tool, the requirements. A surface like this has to:
- Make the authored material — the writing, the work history, the receipts — the single source of truth.
- Be readable by both humans and machines from that one source, with no separate “API” to keep in step.
- Be fast and durable: no runtime to fall over, nothing to patch at 2 a.m.
- Change quickly and safely, so an edit is cheap and a bad edit is caught before it ships.
- Keep its claims honest — what it says is done should be backed by something a reader can check.
Every concrete choice that follows is there to satisfy one of those five. When a choice doesn’t trace back to one of them, it’s decoration, and it gets cut.
§3 The pattern
The architecture, with the specifics removed, is a short pipeline. Content is authored once, in a typed schema; everything else is derived from it at build time; the result is static and validated before it goes out; and the live surface is checked on a schedule to close the loop with reality.
authored content (typed, human-written, the only source of truth)
│
▼
build / derive
├─ rendered pages
├─ sitemap + feeds
├─ machine-readable surfaces (index, plaintext, structured data)
└─ indexes (e.g. an activity log)
│
▼
static deploy (no server, no database)
│
▼
build-time guards (no deploy without passing invariants)
│
▼
live QC on a schedule (does reality still match the build?)That is the whole idea. The rest of this note is just one honest instance of it, so the abstraction has something to stand on.
§4 This implementation
The specific tools that fill in the pattern:
- Astro, static output
The framework. Every route is pre-rendered to HTML at build time; there is no server runtime and no database. It ships almost no JavaScript by default, which is the whole reason to use it for a content surface.
- TypeScript + MDX
Strict TypeScript throughout. Plain Markdown for prose; MDX only where a piece needs components — figures, captioned code, definition lists.
- Content collections (Zod-typed)
Writing, work, static pages, and the Done log are each a typed collection with a schema. Front-matter that violates the schema fails the build instead of shipping broken. This is the “single source of truth, typed” leg of the pattern.
- CSS design tokens
Semantic CSS custom properties — colour, type, spacing — are the single source of truth for look. Components reference tokens, never raw values, so one change propagates everywhere. Dark is the default; light is the same tokens, inverted.
- Self-hosted fonts
Source Serif 4, Public Sans, and JetBrains Mono, bundled locally. No third-party font CDN, so there is no external origin to allow.
- GitHub + Cloudflare Pages
The Git repository is the source of truth; Cloudflare Pages builds it and serves the result from its edge. Package manager pnpm, Node 22.
A collection is a folder of Markdown plus a schema. The schema earns its keep: every entry is checked against a declared shape before it can build, which is what makes editing safe and keeps a stray field from slipping through.
const writing = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/writing" }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
type: z.enum(["Essay", "Note", "Technical note", "Operating memo", "Case fragment"]),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});A piece’s type selects its reading layout; draft: true removes it from the
build, the listings, and the feeds. The whole pipeline is small enough to hold in
your head:
A push to main triggers a build on Cloudflare Pages, which runs the same command
anyone can run locally and publishes the output to a global edge network. Pull
requests get their own preview deployments. There is nothing to roll back beyond
reverting a commit.
§5 What is derived
This is the leg most sites skip, and the one that makes the surface multi-purpose. Everything machine-facing is generated from the collections at build time, never maintained by hand:
/llms.txtand/llms-full.txt— an index of every piece, and the writing concatenated as one Markdown document, for language models.- Markdown mirrors — append
.mdto any writing, work, or Done URL to get the plain source the page was built from, with no HTML to parse. - JSON-LD — Schema.org
Person,WebSite,ProfilePage,BreadcrumbList, andArticledata, emitted from the same canonical data that renders the pages. - Feeds and sitemap — RSS for the writing and for the Done log, plus a sitemap, all from the same source.
The mechanism is dull on purpose: a route reads the collection and emits text.
// One Markdown file per entry, from the same collection the page is built from.
export const getStaticPaths = async () => {
const entries = await getCollection("writing", (e) => !e.data.draft);
return entries.map((entry) => ({ params: { slug: entry.id }, props: { entry } }));
};
export const GET = ({ props }) =>
new Response(toMarkdown(props.entry), {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});Because these are derived, adding one piece of content updates all of them at once. The transferable rule: the source is authored; every other surface is computed.
§6 What is guarded
Derived artifacts can’t drift from the content — but the generators can be wrong, stale, or under-specified. So the maintenance burden doesn’t vanish; it moves from “remember to update seven files” to “keep one small compiler honest.” That is a huge win, and it is enforced by a short list of invariants the build will not break:
- No maintained sidecar files — if it’s machine-readable, it’s generated.
- No runtime unless genuinely necessary — static by default.
- No deploy without passing build checks — the gate is the build.
- No machine-readable surface detached from its source content.
- No claim of “done” without a receipt.
These are not aspirations; they run on every build, and Cloudflare runs the same line, so a violation can’t deploy:
"build": "astro check && astro build && pnpm guard:css && pnpm guard:artifacts"astro check type-checks; astro build renders to dist/; guard:css fails the
build if an inline style attribute slips into the output (it would be blocked in
production by the hashed Content-Security-Policy); guard:artifacts fails the build
if a generated artifact is missing, an entry lacks its Markdown mirror or its
llms.txt line, an internal link is broken, or any JSON-LD block won’t parse. A
separate weekly job runs live QC against the deployed site — pages resolve, the
sitemap is honest, the machine-readable layer is reachable, a missing path still
404s — and opens an issue if reality has drifted from the build.
The security surface is small for the same reason the rest is: nothing runs at
request time. Astro generates a strict CSP at build, hashing the few inline scripts
and styles it controls, with no 'unsafe-inline'; a short _headers file sets the
rest (HSTS, X-Frame-Options, and so on); fonts are self-hosted; there is no
server or database to harden.
§7 What this avoids
Each invariant exists to head off a specific, common failure:
- Sidecar drift — a hand-kept
llms.txtor feed that slowly stops matching the site. (Killed by: generate everything.) - Integration debt — every third-party widget and runtime is a thing that breaks later. (Killed by: static by default.)
- A CMS you now have to operate — the database becomes the project. (Killed by: content is files in Git.)
- A frontend that buries the content — motion and cleverness over legibility. (Killed by: tokens and almost no JavaScript.)
- Generated artifacts rotting silently — the worst kind, because it looks fine. (Killed by: the artifact guard and live QC.)
- “Aboutness” replacing receipts — claiming impact with nothing to check. (Killed by: the Done log and the no-claim-without-receipt rule.)
§8 How to copy it
Strip out the specifics and the recipe is short:
pnpm create astro@latest— minimal template, strict TypeScript.- Add MDX, sitemap, and RSS.
- Define content collections with typed schemas; keep all prose as files in the repo.
- Put every visual decision in CSS custom properties; build a small set of primitives that read only those tokens.
- Add routes that derive machine-readable surfaces — an index, plaintext mirrors, structured data, feeds — from the collections.
- Make the build the gate: type-check, render, and run guards that assert your invariants. Self-host fonts; turn on a hashed CSP.
- Push to a static host that builds on commit and previews on PR. Add a scheduled live-QC check against the deployed surface.
That is the entire pattern. Everything in it is in service of one property: content enters once, and the system does the rest — every time, without adding a chore.
§9 Why this matters
The architecture is the thesis in miniature. A surface where the source is authored, the outputs are derived, the invariants are enforced, and the live state is checked is just reality-coupled action applied to a website: keep the claims, the artifacts, and reality coupled, so the thing can change fast and stay honest at the same time. The specific site is the example. The general system is the public read layer of a Public Interface: small, durable, agent-maintainable, and explicit about what it does not authorize. The public reference implementation contains that reusable pattern without the personal content or private production history.