Most publishing systems treat writing and publishing as separate activities. You draft in one tool, then copy content into a CMS, tweak formatting, and hit publish. The writing environment and the publishing system are disconnected by design.
I wanted something different: a system where publishing is just another view of my thinking. Where the same notes I write for myself can become public posts with minimal friction. Where the blog is an extension of my knowledge base, not a separate silo.
The Architecture#
The system has four layers, each with a single responsibility:
┌─────────────────────────────────────────────────┐
│ Writing Layer: Org-roam + Second Brain │
│ (Knowledge capture, linking, blog management) │
└────────────────────┬────────────────────────────┘
│ ox-hugo export
▼
┌─────────────────────────────────────────────────┐
│ Transform Layer: Ox-hugo │
│ (Org → Markdown conversion, frontmatter) │
└────────────────────┬────────────────────────────┘
│ file output
▼
┌─────────────────────────────────────────────────┐
│ Generation Layer: Hugo │
│ (Static site generation, templating, theming) │
└────────────────────┬────────────────────────────┘
│ git push
▼
┌─────────────────────────────────────────────────┐
│ Hosting Layer: Cloudflare Pages │
│ (CDN, SSL, automatic builds) │
└─────────────────────────────────────────────────┘Writing Layer: Org-roam. Org-roam is a knowledge management system for Emacs. It extends org-mode (Emacs’s outlining and note-taking system) with networked note-taking: every note gets a unique ID, and you can link between notes in any direction. It maintains a database of all your notes and their connections.
Transform Layer: Ox-hugo. Ox-hugo is an Emacs package that exports org files to Hugo-compatible markdown. When you write a note in org-mode and want to publish it, ox-hugo reads the org file, converts the markup to markdown, generates the YAML front matter Hugo expects (title, date, tags), and writes the result to your Hugo content directory. It’s the bridge between the writing environment and the publishing system.
Generation Layer: Hugo. Hugo is a static site generator. It takes markdown files and templates and produces a complete HTML website. “Static” means there’s no database or server-side code; the output is just HTML, CSS, and JavaScript files that any web server can host. Hugo is fast and handles content organization well.
Hosting Layer: Cloudflare Pages. Cloudflare Pages is a hosting service for static sites. You connect it to a git repository, and it automatically builds and deploys your site whenever you push changes. It handles SSL certificates, global CDN distribution, and edge caching. The site loads fast from anywhere in the world.
Each layer knows nothing about the others. Org-roam doesn’t know Hugo exists. Hugo doesn’t know the content came from org files. Cloudflare just sees a git repository with HTML to serve. This separation means I can swap any layer without touching the others.
The Writing Layer: Org-roam Second Brain#
The foundation is org-roam-second-brain, an Elisp package I built to extend org-roam with structured note types and proactive surfacing.
Blog posts are just one of several node types in the system:
- Person: Individuals I interact with, tracking context and follow-ups
- Project: Active work with status tracking and next actions
- Idea: Captured insights with one-liner summaries
- Admin: Life maintenance tasks with due dates
- Blog: Posts destined for publication
Each type lives in its own directory under ~/org-roam/ and carries metadata appropriate to its purpose, but ultimately, they’re all just simple text files. A blog node includes Hugo export properties, draft status, and section assignment. The same tools I use for all my notes, linking, semantic search, daily digests, apply to blog posts.
The package provides blog-specific features:
Draft Tracking. Blog posts have a lifecycle: idea, outline, draft, published. The daily digest shows where each post stands. I can see at a glance which posts need attention.
Semantic Link Suggestions. When editing a post, I can find related notes in my knowledge base that aren’t already linked. This surfaces connections I might have missed, ideas that could strengthen the piece.
Consistent Workflow. Creating a blog post uses the same capture flow as any other note. C-c b B opens a template, I fill in the title and section, and I’m writing. The Hugo metadata is handled automatically.
Writing as the Source of Truth#
The critical design decision is that org-roam files are the source of truth. The Hugo repository contains only generated artifacts, markdown files that could be regenerated at any time from their org sources.
This inverts the typical CMS model. Most systems store content in a database or markdown files that you edit directly. The “official” version lives in the publishing system. Here, the official version lives in my knowledge base. Publishing is a transformation, not a transfer.
The practical benefit: my blog posts exist in context. They’re surrounded by my other notes, linked to related ideas, and editable with the same tools I use for everything else. When I update my thinking on a topic, I update it in one place.
Boundaries and Interfaces#
The interfaces between layers are intentionally simple:
Org-roam → Ox-hugo: Property drawers and keywords. A few lines of metadata tell ox-hugo where to put the output and what frontmatter to generate. The org content itself is standard org-mode syntax.
Ox-hugo → Hugo: Markdown files with YAML frontmatter. Hugo doesn’t know or care that these came from org files. They look like any other Hugo content.
Hugo → Cloudflare: A git repository with a build command. Cloudflare runs hugo and serves the output. Standard static site deployment.
These boundaries are all plain text formats. No APIs, no databases, no proprietary protocols. I can inspect every interface with cat and grep. When something breaks, I can see exactly what’s being passed between layers.
Why This Approach#
I knew from the start I wanted to write in Emacs. The question was how to get from org files to a published site.
Traditional CMS platforms like Ghost and WordPress store content in databases, with web forms as the writing environment. Your content is trapped behind an API, difficult to back up meaningfully, and subject to the platform’s decisions about formatting and features. Not an option if you want to write in Emacs.
Static site generators like Jekyll or Hugo (used alone) are better. Content lives in files you control. But you’re still writing in markdown, disconnected from any broader knowledge management system. Every post starts from scratch.
Notion and similar tools lock your content in proprietary formats. Export is an afterthought. You’re renting your own words.
The org-roam pipeline gives me ownership at every layer. Plain text files I can read with any editor. Open source tools I can modify or replace. Standard protocols between every component.
There’s another reason I wanted plain text: LLM compatibility. Org-mode syntax is close enough to markdown that LLMs handle it fluently. I can paste org content into Claude or use AI tools directly on my notes, and they understand the structure, the headings, the markup. The files are human-readable and machine-readable in a way that proprietary formats aren’t.
Plain text also means maximum output flexibility. From any note in my knowledge base, I can export to LaTeX, PDF, HTML, ODT, or a dozen other formats. The same source file that becomes a blog post could become a printed document or a slide deck. Proprietary tools lock you into their export options. Org-mode lets you choose.
Trade-offs#
This architecture isn’t free. The setup cost is real. Emacs has a learning curve. Org-roam requires configuration. The second-brain package adds another layer. Ox-hugo has quirks.
The system also assumes a particular workflow. If you want real-time collaboration, this isn’t it. If you want a WYSIWYG editor, look elsewhere. If you want to publish from your phone, you’ll need a different approach.
What you get in exchange: complete control, plain text durability, and a publishing system that integrates with how you think rather than demanding you think differently for publishing.
The Bigger Picture#
This blog is one output of a larger system. The same org-roam database powers my project notes, daily journals, and personal knowledge base. Publishing is just one transformation of that underlying data.
The architecture reflects a belief: writing for yourself and writing for others shouldn’t require different tools. The best public writing often starts as private notes, refined over time. A system that treats these as separate activities creates friction at exactly the wrong moment, when an idea is ready to become public but hasn’t yet crossed the gap to a different tool.
By making publishing a single-command transformation of existing notes, the gap disappears. Ideas flow from capture to public expression through the same environment, the same files, the same thinking process.
The code for org-roam-second-brain is available on GitHub if you want to explore or adapt it.