/colophon
Colophon
How this site is built and measured. Every figure on this page is read at build time from the same configuration CI enforces — the table cannot say one thing while the pipeline asserts another.
§ 01 — stack
Stack
Versions below are read from package.json at build; they are the versions this page was built with.
| Layer | Component | Notes |
|---|---|---|
| Framework | Astro 7.1.1 | Fully static output. Zero client JavaScript by default; HTML is rendered once, at build. |
| Interactivity | Svelte 5.56.6 · @astrojs/svelte 9.0.1 | Interactive elements ship only as islands, hydrated when the browser is idle or the element is visible — never eagerly. |
| Styling | Tailwind CSS 4.3.3 | CSS-first configuration. Every color, size, and spacing value resolves to a single design-token file. |
| Content | @astrojs/mdx 7.0.3 · astro-expressive-code 0.44.0 | Articles and guides are MDX in typed content collections; code blocks are rendered at build. |
| Search | Pagefind | Static index generated after the build. Queries run in the browser against local index chunks; there is no search server. |
| OG images | satori 0.28.0 · @resvg/resvg-js 2.6.2 | Social cards are rendered to PNG at build time. Scrapers fetch them; pages never do. |
| Feeds | @astrojs/rss 4.0.19 · @astrojs/sitemap 3.7.3 | Full-text RSS and sitemap, generated at build. |
| Hosting | Cloudflare Workers static assets · wrangler 4.112.0 | The site is static files on a CDN. Nothing server-side sits in the page path. |
§ 02 — zero third parties
Zero third-party requests, enforced
The claim in the footer — 0 cookies, 0 third-party requests — is not a policy. It is a CI gate: a Playwright suite that fails the build if any page reaches any origin other than this site. Per route, the guard:
- discovers routes dynamically — every HTML file in the build output becomes a test, and the discovered set is cross-checked against the sitemap so a published route can never silently drop out of coverage;
- loads each route in a real browser with every network request intercepted — documents, scripts, styles, fonts, images, fetch/XHR, frames, websockets. One request to a foreign origin fails the build;
- scrolls each page end to end, so lazily hydrated islands and lazy-loaded images fire the requests they would fire for a real visitor;
- fails on
preconnectanddns-prefetchhints to foreign origins — those never surface as observable requests, but they leak the visitor’s IP address at the DNS/TCP layer; - parses every stylesheet actually served — linked files, inline blocks, and
styleattributes — forurl()and@importreferences resolving off-origin. This catches the classic CDN-font regression statically, in both color themes at once; - exercises the 404 page for real, via a probe URL that matches no route.
§ 03 — performance budget
Performance budget
The table below is generated from lighthouserc.json, the Lighthouse CI configuration that gates every merge. Every assertion is at level "error": a single miss blocks the merge. Collected 3× per route against the static build, on / · /writing/ · /guides/ · /about/.
| Metric | Assertion key | Budget |
|---|---|---|
| Lighthouse performance | categories:performance | ≥ 95 / 100 |
| Lighthouse accessibility | categories:accessibility | ≥ 95 / 100 |
| Lighthouse SEO | categories:seo | = 100 / 100 |
| Cumulative Layout Shift | cumulative-layout-shift | = 0 |
| Largest Contentful Paint | largest-contentful-paint | ≤ 1500 ms |
| Total transfer, any route | total-byte-weight | ≤ 350,000 bytes |
| JavaScript | resource-summary:script:size | ≤ 60 KB |
| Third-party requests | resource-summary:third-party:count | = 0 |
§ 04 — typography
Self-hosted, subset fonts
All fonts are served from this origin — the CSS scan in § 02 turns a CDN font into a build failure. The Archivo variable font is subset at build into three small WOFF2 files: a 400 and a 700 text face (width pinned to 100%), and a 700–900 variable display face pinned to the 125% width axis and subset to capitals only, since display contexts here are always uppercase. Each file is small enough to fit a single TCP congestion window; rarely used accented characters live in a lazy unicode-range extension file that most pages never fetch.
Loading uses font-display: optional plus local fallback faces with size-adjust and ascent/descent/line-gap overrides computed from the real font metrics — layout is identical whether the webfonts arrive or not, which is how the budget’s layout-shift line holds at exactly 0. Monospace data contexts currently render in a system fallback pre-sized to JetBrains Mono metrics, so the planned mono face can arrive later with zero shift.
§ 05 — measurement
Measured locally
Measurement runs in the visitor’s browser first: the session HUD, the receipt, and the read-depth strip are computed locally and rendered back to the visitor. When a page is left, the browser sends one small beacon to aelmgren.com/api/collect — this site’s own worker, and the only network destination a page ever talks to. The payload is the page path, the referrer, web vitals, and a random session id that lives in sessionStorage and dies with the tab. No cookies, no fingerprinting, and zero third-party requests from the browser — the CI assertions on this page enforce that on every deploy.
Server-side, the worker keeps aggregates only — page path, referrer class (the full referrer URL is never stored), country, vitals — and forwards that same aggregate view to the analytics backends configured at the edge. Vendors are a server-to-server concern here; the visitor’s browser never loads their code.
A full narrative write-up of the measurement architecture is planned. Until then, this page is the reference, and it is rebuilt — numbers included — with every deploy.