May 2025
I’ve been wanting to set up a blog for a long time, but was deterred by the initial setup time and my total inexperience with web technologies. Applying to Google Summer of Code finally forced my hand, as I have to produce easily accessible reports on my progress.
One thing I wanted for it was to use a theme reminiscent of Tufte’s
work.Tufte’s
work distinguishes itself from its recurrent use of sidenotes (such as
this one), as a way to provide context, examples, figures, etc. that are
not strictly necessary to convey the main idea.
This theme was used in several textbooks (such as the Feynman lectures
books), and I liked it enough to want it for my blog.
The first thing I tried was the Hugo
static site generator. While the initial setup was easy, and it allowed
me to write content in Markdown, it broke down when I tried to apply the
Tufte themes that were developed (but largely unmaintained) by the
community, probably because of some deprecated features. I also saw a
similar theme for Jekyll, also
mostly inactive, and after being burnt out from wrangling with Hugo I
didn’t bother giving it a try.
Instead, I decided to take the high road and use Pandoc with the regular tufte-css stylesheet, along with a custom Lua filter to make use of certain features of the CSS, particularly sidenotes.
Pandoc allows converting Markdown to HTML, and features a huge array of language variants and extensions. In addition, it supports Lua filters which operate on its internal AST, making it very flexible.
Pandoc allows passing stylesheets with the
--css
/-c
command switch. When paired with the
--standalone
/-s
, it will insert a
linkA
subtlety is that since it is simply an unprocessed URL, one can pass web
URLs as well, which is what I do for the math font (Libertinus).
to the provided URL in the HTML header.
The Tufte CSS sheet, as well as another sheet for an icon font, are
included this way.
The language variant I use is gfm
(“GitHub flavored
Markdown”), since it supports double-space linebreaks, LaTeX maths,
footnotes,Though
gfm
unfortunately doesn’t support inline
footnotes, at the time of writing. etc. In addition, I enable
the extensions yaml_metadata_block
(which allows specifying a title, author and date for the document in
yaml format), bracketed_spans
(which allows convenience syntax for things like small caps), and smart
(which detects quotes, em- and en-dashes, ellipses, and replaces them
with the correct typographic output).
The Tufte CSS stylesheets needs some HTML tags to function correctly,
namely <article>
which denotes the main body, as well
as <section>
for individual text blocks. These are
injected with a Lua filter which processes the whole document AST,
looking for headings. Top-level headings are enclosed in the
<article>
tags, while others are surrounded with
<section>
.
In addition, the script gives each heading its own local link, and
the CSS will inject an icon which changes opacity when hovered.
Cross-file links are also fixed-up to point to the correct HTML
file.
The Lua filter script converts footnotes into sidenotes that fit the
Tufte theme. This is done by injecting the correct HTML tags to let the
CSS style them correctly.
In addition, figures are given captions through Markdown’s alternative
text. The captions are turned into margin notes by the Lua filter.
Pandoc will perform syntax highlighting on code blocks, and the CSS sheets encloses them in scrollable boxes.
Math is written in LaTeX syntax. Pandoc supports several options for
turning this into web-ingestible content. I chose MathML
(--mathml
), because it is a web-native feature, and does
not rely on pre-rendering or an external service.
However, the rendering isn’t perfect. In particular, Pandoc sets the
stretchy
property to true
on every bracket,
leading to Chrome adding a lot of extraneous
whitespace.See
this
issue.
Firefox is also better at this, for some reason.
Custom code is injected in the HTML header, as well as before and
after the main body, respectively using the
--include-in-header
/-H
,
--include-before-body
/-B
,
--include-after-body
/-A
options. The code adds
a link to the homepage, and some decorations.
The build process is managed by a Makefile, which performs HTML compilation, and copies resources to the web folder. The Make script supports multi-threaded and incremental builds, though the whole process is so fastAround 0.25s for a clean build. that it barely gives a noticeable speedup.
I wrote the homepage HTML by hand, and for convenience I added a target to the Makefile that autogenerates a list of blog posts by grepping the title blocks in the Markdown. At the moment, this list must be updated by hand, though in the future I might automate it.
In addition, the Makefile can launch a simple HTTP
serverI
use python -m http.server
. to test things locally
before pushing to the web.
Finally, deployment is done on repository push from a GitHub action.
Though setting up this website took me a couple days, I’m very satisfied with how it looks, and overall happy about the time I put into it. I also finally have a modicum of web experience.
This page weighs around 400 kB across 11 requests, requires no
JavaScript, and loads in 50 ms according to Chrome Devtools. It could
probably be optimised somewhat but at the moment I’m comfortable with
it.
Building is also extremely fast, and requires no dependencies outside of
Pandoc and common shell utilities.