Serving Websites with Org
Posted on May 17, 2023 by Jacob Walters.
Emacs can browse remote Org files as if they were local. We can exploit this to serve websites entirely with Org mode!
TL;DR: My site can be browsed over Org mode! Go to Emacs Setup to see how.
I wrote in my last post about how my blog posts are written in Org mode. It’s a nice format for writing this sort of thing, with built in support for heading structure, links, images, and much more. It’s also nice for reading, as you can collapse sections, use your favourite Emacs focus package1 for greater comfort, and even link directly to sections from your own Org notes. If you’re reading a technical post, you can even execute code blocks from within the post!
This got me thinking.
Since Org provides such a nice reading environment, why not also serve my site’s content over Org? Hakyll, the site generator I use, makes this straightforward, as I can just tell it to serve the same Org files I write my posts in. Surely Emacs is flexible enough to allow this?
The Emacs Side
It turns out, as always, that it is. There’s a built-in mode called url-handler-mode
that lets us treat remote files as if they’re local.
This works by wrapping the Emacs file handling primitives, which means it affects any function that tries to load a file. Crucially, this includes Org links, so we can link to remote pages and have them download and open entirely with Emacs.
By default, Org won’t download images and render them inline, even if you have inline images enabled by default. Luckily, the variable org-display-remote-inline-images
lets us control this behaviour. You can choose to skip
files (the default), download
them every time, or cache
them whenever the remote file is updated. I recommend the latter.
It’s also a good idea to set org-image-actual-width
to nil
, which allows the use of a #+ATTR_ORG
tag to set the width of an image. I use this to make images smaller to minimise the effect of Emacs’ awful image handling2.
With this, we have pretty much everything we need on the client.
The Hakyll Side
Hakyll is actually a bit finicky when dealing with Org input. It requires a yaml heading with variables for templating purposes, like this:
---
title: This is the title of my amazing blog post
author: Tim Smith
description: A stitch in time saves nine in time, but that's the way we all go.
---
Welcome to my post! Today, we'll be…
It’s possible to define a pandoc compiler that allows you to specify these with the standard Org headers for the title and author, but I use these tags for a few purposes that aren’t supported by the Org headers, so it’s easier to keep using the Hakyll header. Luckily for us, we can strip it away easily using getResourceBody
. We can then fill in the standard Org headers using a template. I made one that also has header links, like the HTML version of my site.
We do still need to produce Org files as output. This basically consists of defining additional matchers for each fie we want to generate, and applying the Org compiler and templates instead of the HTML ones. Hakyll’s version tags make this really easy to do.
Homepage
With this, everything works pretty much flawlessly, except for the homepage. When we access https://jacobwalte.rs
in a browser, it knows to automatically access /index.html
. Org, not being built for this, doesn’t. Forcing the user to remember to add /index.org
is really bad UX, so we need to find some way to automatically redirect Emacs traffic to that page.
Luckily, Emacs has its own user agent! When we browse a site (through url-handler-mode
or an Emacs browser like w3
), we use something like URL/Emacs Emacs/30.0.50 (PureGTK; x86_64-pc-linux-gnu)
as our user agent. You can check yours by connecting to https://ifconfig.me/ua. So if we check against the user agent, we can automatically redirect to the Org file.
Caddy (the webserver I use) can do this easily, with the following rule:
@emacs-ua {
header User-Agent *Emacs*
path /
}
redir @emacs-ua /index.org
This redirects any Emacs traffic from https://jacobwalte.rs/
to https://jacobwalte.rs/index.org
. It’s important that we filter for the root path, as otherwise any file we try to access would bring us to the index page!
This nearly works. Unfortunately, Emacs has no way to tell this is an Org file. The way I got around this was by adding a mode descriptor line to the homepage (and the homepage only):
-*- mode: org -*-
#+title: jacobwalte.rs …
This tells Emacs to use Org for that particular file. With that, everything is ready to go!
Emacs Setup
To browse my site over Emacs, simply add the following to your config:
(url-handler-mode)
(setq org-display-remote-inline-images 'cache)
(setq org-image-actual-width nil)
You can read the full post for an explanation of how these work.
The second and third lines are optional, but basically allow Org to display inline and resized images.
Now, all you have to do is run find-file
(that’s C-x C-f
in vanilla Emacs, or SPC .
in DOOM), and navigate to https://jacobwalte.rs
. You should now see my homepage, rendered in beautiful high-fidelity Org!