Computer science, programming, and whatnot.

Another Static Site

April 02, 2024 back to posts

In January 2016 I moved this website from Jekyll to Hugo.The motivation was to make deployments easier, since Hugo was a single static binary, and Jekyll is not. Over the years, I did a minimal amount of work on the website itself, and as Hugo kept changing, warnings kept piling up every time I, for whatever reason, updated it. In addition, the few times I did want to change something to the site, I inevitabely got lost in the Hugo docs. It became increasingly clear that Hugo was not made for my use-case, and so I wanted to migrate off of it.

This easter I decided to bite the bullet and try something else. I spent an afternoon trying to set up Cobalt, followed by Zola, but they both felt too complex for me. So instead, I decided to write my own, and after a few days I have it all set up:

I tried to keep things simple, and I'm pretty happy with the current state. The code is in one file and is around 450 lines of code. It reads a directory structure like this:
├── pages # Markdown files that are templated.  Directory stucture is kept.
│  ├── # This turns into
│  ├── painting
│  │  └── # ... and this to
│  └── post
│     ├── # This is the page with the list of blog posts
│     ├── flow
│     │  └── #
│     └── static-site
│        └── # ... and so on
├── # Convenience script to build and `fsync` to the server.
├── static # These files are copied to the output folder.
│  ├── iosevka.css
│  ├── post
│  │  └── flow
│  │     ├── bipartite.svg
│  │     ├── flow-graph.svg
│  │     ├── route-connect.svg
│  │     └── route.svg
│  └── style.css
└── templates # Tera templates referenced by the files in pages/
   ├── blog-post.html
   ├── blog.html
   ├── cc-by-sa.html
   └── index.html

The pages directory contains all files that will be transformed to html files in exactly the same directory structure. For every markdown file, the template to use is specified in the front matter. The static directory contains files that should be copied as-is, like css, fonts, or svgs and other assets for specific pages. For instance, the blog post flow is located at pages/post/flow/ and its pictures are e.g. at static/post/flow/bipartite.svg.


There are two sources to Javascript in these blog posts: syntax highlighting and math typesetting.

In Hugo, I had to manually mark blog posts as mathy so that I could include MathJax in the <head> of the template. Initially I ported over the same system here, but I realized that that's only busywork when I have written the generator myself. Now I look for a $ in the Markdown text, and if katex is not explicitly set in the front matter, I set it to true. This way I don't need to specify anywhere that I am using for math, I can just use it. Blog posts that don't use it doesn't include it, and for false positives, katex = false will opt-out.

I do the same with Prism; if I have a code block with a language specified, like ```rust I include Prism, unless prism = false is in the front matter.


I wanted to be able to write markdown and produce HTML, and so one way or another I needed a way of specifying what that HTML should look like. Templates seemed like the least complicated but still powerful enough solution for this. I am not using anything fancy with templates though, it's pretty much accessing fields from the front matter (e.g. katex or date), and formatting the date.

There was one catch however, namely listing the blog posts.

My plan was to read in the directory structure and pass that to the template, but this made it difficult to write out the template, because

  1. The unique identifier (static-site for this post, flow for the Flow post) is in the directory name, and not the front matter.
  2. I wanted to sort the posts based on a date, which is in the front matter.
  3. should be skipped when on the first level.

Tera, like most templaing languages, isn't a joy to use, and so simple data transformations like this turned out to be difficult. However, it has a nice escape hatch in which you can write a Rust function and call register_function to make it callable in the template. That way you can do whatever transformation you want in Rust instead. Convenient, if not pretty.


Instead of writing an HTTP server to serve the files when writing and reloading when any of the files change, I used python -m http.server and watchexec. Maybe there's a nice "hot-reload simple serve http server" out there that would do both for me, but this setup was very low friction. I have to reload the page myself though, but since I mainly write Markdown anyways there's no real reason to have the page update live.

Rewriting the page was also a good excuse to have another look at the CSS, and with it, some nice positioning for <aside> elements, when space allows for it. These are the gray margin notes you can see above. They are positioned with CSS grid, using named columns, and with a @media query for narrow screens to place it back in the regular flow. <code> is also highlighted almost like in my editor now, with mostly white on dark, not too many colors, and bright yellow comments. I'm still not 100% happy with the spacing around certain elements, but it's okay.

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License