Serving Websites with Org

Posted on May 17, 2023

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!


  1. I like darkroom, but there’s also olivetti.↩︎

  2. Try scrolling past an image and claim there’s nothing wrong.↩︎