cmc/cleberg.net
My personal web garden & blog.
clone: git clone https://gitbay.org/cmc/cleberg.net.git
main: content/blog/2026-08-11-orgo.org · raw
1#+date: [2026-08-11 Tue 20:51:00]
2#+title: I Built My Own Org-Mode Static Site Generator
3#+description: I was tired of bolting solutions onto my SSGs, so I built my own.
4#+slug: orgo
5#+filetags: :development:org-mode:
6
7* Introducing: Orgo
8
9For a long time, I've been using [[https://github.com/emacs-love/weblorg][weblorg]], a static site generator (SSG) for
10org-mode. It uses Emacs Lisp, which pairs well with Emacs. However, it has a few
11downsides:
12
13- since it requires Emacs, the set-up and maintenance is heavy
14- it has upstream dependencies that must also be installed and loaded into Emacs
15- it re-runs the full process every time you need to regenerate the site, even
16 if you only change a single character in one file
17- since it runs through Emacs in the background, it takes quite a while to build
18 my site (~180 pages)
19
20So, I decided to spin up my own org-mode-based SSG: [[https://github.com/krazywarez/orgo][orgo]]. This SSG uses org-mode
21(rather than markdown) for its content.
22
23#+caption: The orgo CLI
24#+attr_html: :alt Terminal output of orgo --help, listing the build, watch, serve, clean, audit, and init commands.
25[[https://img.cleberg.net/blog/20260811-orgo/orgo.webp]]
26
27* Results Up Front
28
29I want to highlight the speed first, before I go into the major differences. As
30you can see in the table below, orgo is incredibly fast.
31
32| Method | Time | Diff |
33|----------------------------------------+-------+-------------|
34| weblorg (=emacs --script publish.el=) | 49.0s | |
35| weblorg + [[https://github.com/ccleberg/cleberg.net/blob/8ec9cdfeae71068a8924dd9f61b9cc28c947ec31/build.py][build.py]] | 50.3s | |
36| orgo, cold build | *0.22s* | 223× faster |
37| orgo, nothing changed since last build | *0.13s* | 377× faster |
38
39Additionally, I added a few new features that weblorg didn't provide me:
40
41- Native features I used to build in Python post-weblorg:
42 - Tag listings
43 - Year separators on the blog page
44 - RSS feed
45 - Sitemap
46 - Recent posts lists
47- Syntax highlighting for 75 languages is supported, and you can drop in your
48 own syntax definitions for anything missing.
49- Builds are incremental. Only the pages whose content, config, or templates
50 changed get re-rendered — adding a single post re-renders that post and the
51 index that lists it, and nothing else.
52- There is no Emacs, no package manager, and no runtime to install since orgo is
53 a single binary.
54
55This results in a fast, clean, and simple SSG that stays out of my way instead
56of requiring extensive set-up and maintenance.
57
58* What It Is Built On
59
60/Note: This section is for the nerds. Feel free to skip if you don't care about
61the technology behind orgo./
62
63orgo is Rust, and it leans on a handful of well-worn crates rather than
64reinventing them:
65
66- The org parser is hand-written, about 1,500 lines of it. There is no org-mode
67 crate to lean on, and writing that parser is most of what writing an org SSG
68 turns out to be.
69- Templates run on [[https://crates.io/crates/minijinja][minijinja]], which speaks Jinja2. weblorg's templates are
70 written for templatel, which is close enough that porting them was mostly a
71 rename job.
72- Syntax highlighting is [[https://crates.io/crates/syntect][syntect]], so the definitions and the themes are Sublime
73 Text's. This site renders code with =InspiredGitHub=.
74- Incremental builds hash the content, the config, and the templates with
75 [[https://crates.io/crates/blake3][blake3]]. A page is re-rendered when its hash changes, and the manifest that
76 remembers them is JSON sitting in the output directory.
77- Pages render in parallel with [[https://crates.io/crates/rayon][rayon]], which is straightforward here because
78 each page writes only its own file.
79- The config is TOML, the CLI is [[https://crates.io/crates/clap][clap]], and =orgo serve= is [[https://crates.io/crates/notify][notify]] watching for
80 edits with a [[https://crates.io/crates/tiny_http][tiny_http]] server in front of the output.
81
82* Getting Started
83
84If you use org-mode (or have been looking for a proper org-mode SSG), check it
85out and let me know what you think.
86
87It's as simple as installing, initializing a new site, and serving it!
88
89#+begin_src shell
90cargo install orgo
91orgo init my-site
92orgo serve my-site -o _site
93#+end_src
94
95It can create a new project for you, adapt to your current org site, or simply
96serve your folder of org-mode notes and files.
97
98Three more commands cover the rest of the day-to-day:
99
100- =orgo watch= rebuilds incrementally as files change, driven by filesystem
101 events, without serving anything. This is handy when something else is already
102 serving the output directory.
103- =orgo clean= removes the output directory, cache manifest and all, for when you
104 want to start from scratch.
105- =orgo audit= reports which org constructs a folder actually uses, and marks the
106 ones it doesn't handle.
107
108#+caption: An orgo Audit Report
109#+attr_html: :alt Terminal output of orgo audit, tallying every org construct in a corpus by frequency and marking each one in or out of scope, followed by counts of keywords, block types, drawers, and link schemes.
110[[https://img.cleberg.net/blog/20260811-orgo/audit.webp]]
111
112* The Future
113
114I'll be looking into making some built-in themes soon and exploring other
115features that make it a bit more seamless, as well as adopting to various
116methods different users may expect it to work.
117
118If you have any suggestions, reach out and let me know or open an issue/PR on
119GitHub!