I decided to redo my website using Hugo (before I was using Jekyll). Both are static site generators: you write in Markdown, a simple clean formatting language, and it generates HTML webpages following a consistent format. It’s great because the generation only has to occur once after the Markdown is written, and nothing active has to happen for each user of the website (unlike a so-called dynamic website). This reduces costs, increases reliability, and improves securitythe user needs very little access to the server hosting the webpages in a static site
. The problem I was having with Jekyll was that it was taking about 40 seconds to generate the site, which meant that if I wanted to make a small change and see how it looked, I had to wait for it to generate to see the result (like a long LaTeX compilation). Hugo is super fast; my site takes less than a second to generate fully now.
While you’re still reading, and before I get into the details of why I switched and how the site is setup now, one thing this means is I can use the very cool git info
feature which gives me last modified
dates taken from the git repository in which the blog lives. I put these dates all over the place, because
- I think it’s cool
- Occasionally one finds a page which they really want to know if it’s up-to-date, and it can be hard to tell.
These dates only change when content of the page changes, not the literal file you’re seeing. That’s useful, because the literal file could change for a bunch of other reasons:
- I change a template a little bit, which changes the formatting of the page slightly
- I change the css to change how something looks (possibly on another page); since some of my css files have subresource integrity, if the css file changes, a hash on this webpage changes, and so the file is changed.
- Or even just that I regenerate the site when making a new post or etc and the file gets regenerated, changing some timestamp.
So these last modified
dates should be accurate and hopefully useful. Additionally, if the “official” date I type into the frontmatter of a blog post (which usually corresponds to when I start writing it, or when I post it) is different by at least a day from this “last modified” date, then my website code adds an asterisk and puts the last modified date too. So every older blog post will have this, because by adding them to the new site, I changed this “last modified” timestamp, which will thus differ from their original posting date. I’m ok with that, because I also did modify all of them; the way I typed math on the old site was via these terrible {% math %}
commands, whereas now I just use $
like in LaTeX. So I at least had to change that, and often fixed up the wording or changed the content a bit as I was reading through them. So many of them were legitimately modified on the day that the asterisked “last modified” timestamp says.
Turning back to Hugo, while the main reason I wanted to change was generation speed, it turns out that it has a lot of nice features that make the things I was trying to do much simpler than with Jekyll. For example, the way I made HTML solutions for the example sheets for the QIT course was a little complicated; I wrote a single markdown file for the solutions, then wrote a script to split it into separate files for each exercise and solution, which I compiled to HTML using pandoc. Then the page for Example Sheet 3, for example, works by paginating over these separate files. Why not just have one file? Well, that ability to paginate for one. Also, separate files makes it easy to make the search index, and it relates to other ideas I want to explore about compartmentalizing information.
But to do this in Jekyll required making a new “category” for each example sheet, which requires a new top-level folder, a change in the site-wide configuration file, etc. In contrast, Hugo has “branch bundles” which allow one to do this easilyWell, once one gets the hang of how to do things in Hugo.
. These are a type of “page bundle” which allow you to keep resources associated to a page (like images, files, etc) in the same folder as the text of the page, which turns out to be pretty convenientIn my old blog, I had to use a top-level /static
directory to put all the images, etc.
.
Moreover, at the time I originally made each different type of page, I was focussed on the immediate need and not the overall picture. Thus, a rewrite allowed me to think over more what kinds of things I really wanted and how to implement them.
I’ve found Hugo a bit strange at first. It works by a system of templatesAKA layouts
that override each other, kind of like CSS. Ignoring “taxonomies” for nowwhich I’m only using fairly minimally, for tags
, there are two types of pages: “sections” AKA “list pages”, and “pages” AKA “single pages.” The page /blog/ is a “list page”, and its purpose is to list the blog posts. Below that in the hierarchy are the individual blog posts, which are single pages. There’s a default file /layouts/_defaults/list.html
which provides a template for list pages, but this can be overridden by layouts/blog/list.html
which is for list pages under the blog
heading. Likewise, there is a default “single page” template under /layouts/_defaults/single.html
, but for blog posts, this can be overridden by layouts/blog/single.html
. Moreover, in the frontmatter of a particular page, you can explicitly set the layout to something else, to have custom layouts for particular pages.
What’s the big deal about templates? The most obvious thing is that they provide a uniform template so all the blog posts look the same; for mine, they load in the right CSS to make the sidebar for the sidenotes, load KaTeX for parsing math, etc. But the other important thing is that in Hugo, the templatesand for the most part, not the actual pages themselves
have access to programmatic functions and variables. So a template for a list page has access to the equivalent of for
loopsrange
in Hugo
to loop over the pages and show an excerpt from them, etc.. This is exactly what I do for the example sheets: the page you see is secretly a list page, which is listing the “pages” below it, which are the individual problems and solutions. The way it lists them is it just spits out the full contents of the page.
So most the process of making of my website is just creating the right hierarchy of template pages under /layouts
in order for each page of the website to grab the right template for its particular useand writing those templates of course
.
Hugo also has partials
, which are just parts of templates abstracted out so you can reuse them in different templates, and views
, which are basically alternate ways to present some of the content of a page. For example, my blog posts have a view
which is just a bullet point with the date, title, and a brief description (taken from the frontmatter of the post itself), which is what the list page /blog/ renders to make its list.
I think Hugo has a really nice combination of flexibility (overriding templates, alternate views, etc), along with enough rigidity to help ensure it doesn’t become too much of a mess. What rigidity is that? Well, you only have access to programmatic tools in the templates and not in the text of the posts. As I understand it, this is meant to be a separation of the logici.e. code
of the website from the content, to encourage a uniformity in how the site operates despite varying content.
I’m happy with the new system, and think it will make adding new types of pages, along with more things like example sheets, much easier in the future.
Some more details for the curious:
I started from the hugo classic theme, which is where the nice purple comes from, along the navbar and the essential style. I actually added the bit that extends the file path analogy when you go to a subsectionFor example, go to /teaching/qit/es3/ and look at the purple nav bar at the top; you should see
~/teaching/qit/es3
, where each bit of the path is link to the corresponding page.
. For the blog posts, I cheated and just put~/blog/post...
since the URLs can be long, and the intermediate pages like/blog/2018
, aren’t real pages so it doesn’t make sense to link to them.My search bars work pretty much according to this gist, though with some modifications (some written here and also some changes to have different local searches just for the example sheets, etc.). Basically Hugo generates JSON files which act as search indices when it generates the site, and some client-side javascript uses these for the actual search.
I’m actually using
blogdown
which runs inR
, a stats programming language. This is mostly overkill for what I’m doing, but it makes a few things easier. It allows one to write inRmarkdown
, a variant of markdown to allowR
code snippets to be (pre)-executed and displayed in the document itself, which is really cool. I don’t useR
, but supposedly it works forjulia
andpython
too. But more usefully for me,Rmarkdown
allows theorem numbering and cross-references to theorems, and usespandoc
to generate the HTML files from markdown. It’s great that it usespandoc
, because I’ve been using that a lot for markdown and can still use features from there. For example, I’m using thepandoc-sidenotes
filter to automatically translate markdown footnotes into sidenotes. If I writeblah blah^[note blah].
then you see: blah blahnote blah
. Nice! Also, I can write LaTeX-style macros, like\newcommand{\eps}{\varepsilon}
at the top of the file, and
pandoc
will essentially find-and-replace all “\eps
to”\varepsilon
", which is very convenient.The
/work
section is similar to the example sheets: each type, like/work/essay
is a “branch bundle” which has pages that are just files describing one particular work. Then/work/essay
is a “list page” which lists those works. Finally,/work
is a list page which lists all of the works in its child pages (/work/essay
,/work/poster
, etc.). The style is stolen from the arXiv widget that I use for listing my arXiv papers.