docsยทupdated apr 20
colophon.
The Obsidian vault is the source of truth; a small build script turns it into the pages you're reading.
folder layout01
Two repos. Vault stays private; site repo is thin and public. A pre-build script clones the vault into
./vault/ using a fine-grained PAT โ not a submodule, since CF Pages' GitHub App auth doesn't reach submodule clones.โ vault (private ยท obsidian) โโโ daily โ โโโ YYYY-MM-DD.md # micro-posts ยท one ##HH:MM = one thought โโโ notes โ โโโ dev # โ /journal/<slug>/ โ โโโ making # โ /making/<slug>/ โ โโโ ideas # never published โโโ attachments # images / audio / video โโโ .obsidian/ # ignored โ mattdoes-site (public) โโโ build.js # the generator โโโ site.config.js # identity + last.fm โโโ templates โโโ vault โ cloned pre-build โโโ dist # deployed
frontmatter schema02
Every note that wants to appear on the site declares it.
publish: is the only required field.| key | type | req | description |
|---|---|---|---|
| publish | enum | req | journal ยท thoughts ยท making ยท draft (listening is pulled from last.fm) |
| title | string | opt | Display title. Defaults to filename. |
| date | date | opt | ISO 8601. Defaults to file mtime. |
| slug | string | opt | URL path segment. Defaults to kebab filename. |
| tags | [string] | opt | Render as #tag. Drives filter rows. |
| summary | string | opt | One-sentence lede. Shown in index + RSS. |
| status | enum | opt | seed ยท growing ยท evergreen |
| updated | date | opt | Last meaningful edit. |
| cover | path | opt | Image in vault/attachments/. Rewritten at build. |
| aliases | [string] | opt | Extra wikilink targets. |
routing ยท path โ url03
notes/dev/<slug>.mdโ/journal/<slug>
notes/making/<slug>.mdโ/making/<slug>
daily/YYYY-MM-DD.md ##HH:MMโ/thoughts#t-NNN
attachments/<file>โ/img/<file>
last.fm recent tracksโ/listening/
notes/ideas/*.mdโnever built
wikilinks & embeds04
[[some-slug]]โ resolves to published page; else renders as plain text with a dotted underline.[[some-slug|custom label]]โ aliased label.![[image.png]]โ image embed. Served from R2 in production.![[clip.mp3]]โ renders as native <audio>.[[#some heading]]โ intra-page anchor. Rare.
the build05
// simplified
for (const note of walk('vault/')) {
const { data, body } = matter(note)
if (!data.publish || data.publish === 'draft') continue
const html = renderMarkdown(body, {
wikilinks: resolveAgainst(publishedNotes),
embeds: rewriteToR2()
})
const route = routeFor(note.path, data)
write(`dist${route}.html`, template(data.publish, { data, html }))
}
writeIndex('dist/index.html')
writeFeeds('dist/feed.xml')
Four templates, three content types, one loop. If a new section shows up (say, publish: recipes), it's a new template file and one line in routeFor(). That's the whole extension story.