Creating a Blog in Org-Mode in 2020
Published:
I just finished1 building a new blog org-mode. You're now looking at that blog (I should think). As is customary for any new blog designed in a relatively obscure way, I shall attempt to justify my technical choices and also assemble a rough guide for you to build your own "blorg."
What Doesn't Work
This is the fourth or fifth blog I've constructed in the last few years. I used Jekyll for quite some time, before moving to Metalsmith + Pug, then Netlify + GatsbyJS, then finally Ghost hosted on a Google VPS before deciding to build my current system. I reached a point where I actually began to version my personal website, adhered to semver, and tagged releases on GitLab. I will try to explain, briefly, why each of these setups was either insufficient for my (relatively meager) needs, or just rubbed me the wrong way.
- Jekyll
- In my opinion, Jekyll deserves its popularity. Few static site generators manage to have as low a barrier to entry as Jekyll has while avoiding cruft and slowness. However, this is also Jekyll's main flaw — there is very little you can do with Jekyll beyond write posts in Markdown, attach templates to them, and publish them as HTML. If all you plan on doing with your website is authoring simple posts and publishing them, and you want to do this efficiently, Jekyll is probably right for you. If you, like me, are never satisfied unless you can push the software you use to near-ludicrous limits, Jekyll is something you will outgrow very quickly. It is very difficult to use source formats other than Markdown with Jekyll — there are org-mode packages which claim to work, but, in my experience, they do not.
- Metalsmith
My search for extensibility led me to Metalsmith, which uses a rather complex mix of GNU makefiles and JSON for configuration and compilation. Metalsmith, if you are unfamiliar, provides a "black box" which you can connect arbitrary sources and destinations to. You can tell it that you'll hand it content in Markdown, HTML, Pug, and org-mode syntax, and that you want that content converted to HTML, and it will just work provided you have a proper plugin.
The main problem I had with Metalsmith is that it is extremely slow. My GitLab CI deployments would oftentimes take upwards of ten minutes to complete, which, as someone who likes to stare at deploy logs as they get produced, was annoying. Also, I made the error to begin by writing content in Pug, which is not a very good template engine and has poor support in editors. By the time I had a technical stack I was comfortable with, I had installed so many plugins that Metalsmith was struggling to cope. Eventually I decided that is was more worth my time to dump Metalsmith entirely and rewrite my content in something more focused.
- Netlify CMS
- Admittedly I did not spend much time here — I was unable to get GitLab's default Gatsby setup to work with Netlify, even though they both advertise built-in support for each other. This may have been my fault, but I did spend some 20 or so hours fiddling with settings and configuration files to get it to work, to no avail.
- Ghost
Ghost is good software. It is the only WordPress competitor I have discovered which I would recommend to people with a straight face. I am actually currently building a large website for a client in Ghost, based partly on my experiences using it for my personal blog. It isn't as featured as WordPress, but it is much faster, more modern, and provides more out-of-the-box compared to WordPress's "There's a plugin for that" culture.2
After a year of using Ghost, my GCP free trial expired and Google shut down my VPS. While I could have reinstalled Ghost onto a new system, I decided that it was time I returned to static hosting — I missed org-mode too much!
The Road to Org
Org-mode, and Emacs in general, have absorbed aspects of my life in a roughly linear trajectory since I first discovered Emacs in 2012. I have an org-roam database full of thousands of lines of my academic notes, notes on literature, journal entries, contacts, etc. Org's literate programming features simply have no peer right now. When I try to use any other markdown format, I'm uncomfortable at how barebones they all are. Painless export to HTML, LaTeX, presentations, etc. (even simultaneously), backporting of some of LaTeX's more seamless syntactical features to other export modes, the footnote syntax, table manipulation; all of these things I hate to live without and am increasingly finding myself unwilling to. So I decided I would use org-mode for my blog. But this means not using the watered-down org-mode "renderers" that exist on GitLab, GitHub, etc. — all of which are missing critical features, sometimes basic ones such as boldface. I need Emacs to do the heavy lifting.
I tried ox-hugo but its setup was tedious to the point of absurdity. I started writing a generator to export org-mode files to Zola's syntax, but this turned out to be equally too much work and not enough benefit. Without basing any generator on org-mode's extant output formats, some of org's strengths (such as execute-on-export for literate programming) vanish. Ox-hugo gets around this by exporting to markdown rather than building a novel Hugo front-end, but in my opinion this is not the right attitude to take towards building an Emacs-based blog. Org-mode already has an HTML exporter, why go through a less-featured middle-man?
That's how I arrived at org-publish, which is [1] built-in to org-mode, [2] supports all features and even allows for some custom configuration, and [3] is extremely simple.
Publishing from Org
To use org-publish, you simply assign a directory of "source" posts, a directory to export those posts to, and any settings you would like applied during translation. These settings are defined just like any other emacs settings, using Emacs Lisp in your configuration directory or a custom file. The tight integration with regular Emacs configuration means you have free reign to set up the system however you want — for example, I wrote a function which loads a deploy script (also written in Emacs Lisp) before calling org-publish. That script acts as a sort of makefile: calling sassc
to compile my Sass files into CSS files and placing them in the proper directory, running surge
with the proper settings to deploy the static files, etc. Org-publish includes a feature to generate a "sitemap" out of the box, which I use as my blorg's landing page.
Once that is set up, simply calling my function from anywhere in Emacs will publish (export), build (compile), and deploy my blog. No more waiting around for GitLab's CI to spin up; no more 100-commit days where syntax errors in obscure configuration formats brought down the runner, no more refreshing my home page every few seconds while guessing if Cloudflare is just taking awhile or if the deployment really broke (again). And Google won't shut off my server again.
For convenience, I'll include some snippets of the configuration I wrote here. However, I intended this setup to be deeply integrated with the rest of my Emacs workflow — I define blog posts with an org-capture, which I have bound to C-c n c p
; a lot of my HTML configuration is defined globally rather than just for blog posts; etc. My entire Emacs configuration is hosted on GitLab3 and is publicly licensed, so you can take a peak at the monsters I've assembled if you'd like. For example, you can find the org-capture template here and my general org configuration (including all of my configuration for org-publish) here; note the hard-coded fully-qualified filenames.4 Change that if you intend to remix what I've written.
My deploy script is in the root directory of the blog, and contains only two lines:
(shell-command "sassc -a src/sass/index.sass css/index.css") (shell-command "surge public blog.ericlondr.es")
Again, note the use of fully-qualified absolute filenames. The key function in my global Emacs configuration is this:
(require 'ox-publish) (defun blog-deploy () (interactive) (let ((wd default-directory)) (org-publish-all) (cd "~/org/blog/") (load-file "deploy.el") (cd wd)))
blog-deploy
publishes the blog and then runs the deploy script, returning Emacs's session to the proper working directory afterwards.
Take note that there is nothing special about the deploy script. It doesn't need to be Emacs Lisp, I just decided I liked it like that. You could write a makefile, or use some other scripting languages such as bash or Python or even Rust, or include the deployment commands in the blog-deploy
function (once the directory is set). That's one of the beauties of this setup — the ecosystem is completely divorced from the static file generation, so any additional configuration or compilation steps you want to do you can just script yourself.
The capture template I use for starting off new posts is this:
#+title: %^{Name} #+date: %t #+subtitle: Published: {{{date}}} #+setupfile: /home/eric/org/blog/org-template/style.org %?
When you run org-capture and call this template, it will ask you first for the slug (filename), and then for the title ("Name"). The date will be automatically inserted. To render the dates as I have, eg in this post, customize org-time-stamp-custom-formats
and then run C-c C-x C-t
in the new buffer. The setupfile listed here will be included verbatim when org-publish converts the file to HTML. My setup file is very basic, but it would be difficult to accomplish this using other features:
#+html_head: <link rel="stylesheet" type="text/css" href="../css/index.css" /> #+html_head: <link rel="stylesheet" type="text/css" href="https://fonts.imma.link/to/latin-modern.css" />
This loads my site CSS and preferred webfont in each HTML page. It's infeasible to include this in the capture template, because the capture template will not apply to the sitemap (index.html).
That's pretty much it. Beyond some specific customizations of HTML flavor and export options, that's more or less all you need to get a robust yet simple blog built in Emacs. I tried to keep this setup as simple as possible, despite the somewhat esoteric nature of it all — overall, I wrote less than 50 lines of configuration + code (which, in Emacs, is the same thing), installed exactly 0 external packages beyond SASS and Surge for deployment, and ended up with a no-JavaScript, no-build-times, no-external-dependencies,5 zero-cost blog.
Not bad for my fifth try.
Next Steps
Hopefully the fact that I can author posts in what I consider to be my native format will encourage me to write more. It probably won't. But, for the time being, I'm happy with this setup.
My Emacs configuration is all copyleft already. I intend to license the non-source-code content of this blog openly as well, but have not yet decided which license to use. I am deciding between the GNU Verbatim Copying License and CC-BY-ND.
If you have any questions about my setup or anything else, feel free to message me on Telegram.
Footnotes:
Or, about as "finished" as one can be on a hobby project that will surely involve hours to days of tinkering during the period of its use.
I'm not saying "There's a plugin for that" is an inherently bad way to organize an ecosystem. Emacs is somewhat similar in this manner. However, full-stack website CMSes such as WordPress and Ghost certainly benefit from reducing dev time as much as possible (if dev time was no concern, why use such a monolithic tech stack?), and in this regard Ghost definitely appeals to me more than WordPress.
You can use relative filenames if you want. However, this breaks my global export functions if you run them from outside the blog directory. org-publish-all
should still work from anywhere, however. I briefly experimented with writing a git hook to circumvent this, but decided it was too much effort.
If one were so inclined, slight modification of my code could turn this into a no-internal-dependencies project either; if you added my HTML customization and org-publish configuration to deploy.el
, you could add a shebang calling emacs -ql
in the top line of the file and replace the whole damn thing with a shell alias that invokes that file as if it was a program. At that point, the fact that you are even using Emacs is transparent, and hidden. You now have a static site generator like Jekyll or Hugo, except infinitely more powerful. If you're in love with CI and automated testing, you could easily tell docker to spin up Emacs and just run the file — no local installation necessary! This does not mix well with my extensive usage of Emacs (especially daemonized emacs), but there is nothing preventing one from setting this up.