This page documents the domain it is served from. If the setup drifts, fix this page.
The domain is a hybrid: cheap content lives at paths on one worker, real apps get their own subdomain.
| Thing | Where | Why |
|---|---|---|
| Reference pages, notes | stevex.us/ref/*, stevex.us/learn/* |
A markdown file shouldn't need its own deploy |
| Anything with state, a build, or its own stack | thing.stevex.us |
Independent deploy, independent blast radius |
The rule of thumb: if it's markdown, it's a path. If it's an app, it's a subdomain.
There is no gateway proxying paths to backends — the apex worker serves paths itself, so it can never take a subdomain project down with it, and a subdomain project can never take the hub down.
# 1. write it
content/ref/my-thing.md
# 2. build + deploy
npm run deploy
That's the whole loop. Frontmatter is optional; without it the title is derived from the filename and the description from the first paragraph.
---
title: My Thing
description: One line for the section index and the meta tag.
updated: 2026-08-16
order: 10
index: false
---
order — sort position within the section (default 100, lower floats up).index — defaults to false, which emits noindex,nofollow. Set it to true only for a page
you actually want in search results. Half-finished notes stay out of Google by default.Make a folder under content/ and drop a _section.md in it:
---
title: Recipes
description: Shows up under the card on the hub.
order: 30
hidden: false
---
The hub, the header nav, and the router all pick it up from the filesystem — there is no registry
to update. _section.md is metadata only; it never renders as a page.
thing.stevex.us in that project's wrangler.toml:[[routes]]
pattern = "thing.stevex.us"
custom_domain = true
PROJECTS[] in src/index.js so the hub links it.⚠️ Never mix dashboard-attached custom domains with declarative
[[routes]]on the same worker — a deploy detaches the dashboard ones. Learned the hard way during the stevexsports.com apex cutover.
A subdomain doesn't have to be a Worker. It's a CNAME; point it at Vercel, Pages, a container, whatever the project wants. Workers is the default because it's the fastest path, not a rule.
build.js walks content/, renders each file with marked, and writes src/content.gen.js — a
plain JS module of pre-rendered HTML strings.
JSON.stringify, so markdown never needs hand-escaping.
Write $, backticks, backslashes freely.The tradeoff: content changes require a deploy. For a handful of reference pages that's the right trade — and it means the site cannot break from bad data, only from bad code.
| Path | Serves |
|---|---|
/ |
Hub — section cards + project cards |
/<section> |
Section index |
/<section>/<slug> |
A page |
/healthz |
ok |
/robots.txt |
Allow-all; per-page meta tags do the real gating |
| anything else | 404 |
www.stevex.us 301s to the apex, path and query preserved. Trailing slashes 301 to the bare form.
npm run build # markdown -> src/content.gen.js
npm run dev # build, then wrangler dev on localhost
npm run deploy # build, then wrangler deploy