When I decided to start a blog, the first question was “which platform?” Velog, Tistory, Medium, Ghost — all solid options. But as I went through each one, I kept running into the same problem: it never quite felt like my content was fully mine. Either I was locked into a platform, or I had to run a separate database.
So I built my own. With one condition: AI must be able to write and publish posts directly from the CLI.
That meant no database.
AI reads and writes files naturally. But querying a DB, registering through a CMS API, uploading media — that gets complicated fast. If dropping a markdown file into a folder was all it took to publish a post, everything would be much simpler.
The stack: Astro 5. A natural fit since the content is all markdown. It’s a static site generator, so no database needed, and Content Layer lets me use the filesystem directly as a content source.
The content source is my second brain wiki.
The structure is simple: series-named folders under export/slowloop/blog/, with markdown files inside. Astro reads this path via a CONTENT_DIR environment variable using a glob, and treats everything it finds as publishable content.
This makes the second brain wiki the true single source of truth. Writing, editing, and managing publish status all happen in the wiki — one frontmatter field at a time. The file is the database.
second brain wiki/
export/slowloop/
blog/ ← KO posts (Astro reads this)
slowloop/
second-brain/
...
blog_en/ ← EN posts (same structure)
content-drafts/ ← drafts (excluded from build)
Bilingual publishing is handled by running two parallel folder structures.
blog/ (Korean) and blog_en/ (English) use identical slugs. When a post is published, both the Korean original and English translation are created at the same time. No direct translation — the English version is rewritten in natural English while preserving the author’s voice.
On the site, a language toggle switches between the two versions of the same slug. If no translation exists, the toggle is disabled. Language preference is saved in localStorage, so it persists across visits.
Cross-post connections are built in two layers.
The first is inline body links. Writing [[slug|display text]] in the body gets converted to real post URLs at build time. If the target slug hasn’t been published yet, the link is disabled and only the text is shown.
The second is the related posts section at the bottom of each post. It’s generated automatically from the references: [slug1, slug2] field in frontmatter. At publish time, the full post corpus is scanned for slugs that share topic, tags, or key concepts, and the field is filled in automatically.
Publishing a single post triggers updates everywhere it connects.
Add a post to a series and the series index page updates automatically. Any other post that references this one via references gets its related posts section refreshed too. Tag pages, search index — all of it reflects from a single new file.
There’s no “I published this, now I need to go manually add it to the series page.” The file is the database; the build is the sync. Touch one file, the entire content graph follows.
The full lifecycle of a blog post looks like this.
It’s born as a markdown file inside the second brain wiki, published to export/slowloop/blog/, and appears at slowloop.app/blog once Astro builds it. Edits are just file edits. To hide a post, add status: archived to the frontmatter.
No database means AI can manage everything directly. No editor UI needed. It all runs from the CLI. That was the goal from the start.