Creating a personal website with Hugo

Until recently I built my personal webpage with Nikola (see this post). However I decided to change that when moving to GitHub pages, in part because I thought Jekyll would be easier to use on GitHub, in part because after a Nikola upgrade the theme I was using broke and didn't know how to fix it. The problem is probably ignorance on my part more than anything else. I am no expert on web publishing and can't afford to become one, I just need to put up a decent website (and I hate CMSs).

However, reading Jekyll documentation scared me a bit, and a dear colleague pointed me to Hugo. I followed his suggestion, and I managed to set up a rather nice, if not perfect, website. Now that it's done, it seems I have a setup where I can easily enough create and update content in a bunch of plain text files (stored in GitHub), quickly create a site and publish it through GitHub pages. However, I'd lie if I said setting it up was a breeze. I am certainly no web expert, but I have been around computers for a while (my first encounter with them was through my father's TRS-80 Model I), and this is not my first time with a static web site generator. Yet I had a hard time at first.

So I decided to write this summary of my experience and what I gather about how Hugo works. It will serve mainly for my own reference, but it might also be useful to others, so I publish it here. When the Hugo docs failed me (which at first was very often), I found much help in this post from Sara Souiedan's blog, and in this one by J. P. Droege.

Understanding Hugo

My main problem with Hugo is documentation. Not the not lack of documentation: there is plenty of it. However, it seems designed to explain Hugo a detail at a time, and there is no "high-level" or big-picture summary of how Hugo works, and how a source directory structure is turned into a web site. There are also very detailed guides to install and work with a particular theme. In fact I started with one of those, which is fine if you plan to use the theme exactly as it comes. However, as soon as I wanted to tweak a minor detail, I stumbled due to lack of understanding of how Hugo interprets your source. What follows is what I eventually gathered, from the mentioned blog posts, the docs, and a lot of trial and error.

This is not intended as a guide to use Hugo to build a complete web site from scratch. I don't know enough to do that, and I am using a theme, which fills in the CSSs and other stuff needed to get a nice-looking site. I am just trying to get enough understanding of Hugo's workings to be able to do good use of a theme (note again that I'm not an expert and I could be wrong in what I say, and that I will be happy to stand corrected if someone with more knowledge cares to leave a comment).

After installing Hugo (I did so through my Linux distro package manager), one creates a bare working tree (starting at, say, ./test/) with the directory structure Hugo expects with

hugo new site test
cd test

Essentially, Hugo will create a site in the public/ directory by applying a series of transformations to the content of the content/ directory. These transformations involve various things I don't understand completely, but basically Hugo reads your content from a source tree starting at content/, plus a set of templates (which you write or get as part of a theme) and a set of data (defined in the header of the source file themselves or in various configuration files) and writes .html files to pulibc/. Additional files needed for the site (like images and .css) are copied as-is from the static/ directory. The templates (though Hugo does not call them that, but layouts or archetypes) and other stuff that formats your content (and in principle does not change as often) are in archetypes/ and layouts/.

The site is created when you invoke hugo on the root of your source site. To draft and debug the site, you can call hugo serve and your site is served locally, and automatically updated upon any source tree change (and very fast). This is a great feature.

Source files are normally written in some markup language that makes your life easy. Markdown (.md) seems to be the standard for Hugo. I'm a fan of Emacs and Orgmode, and luckily Hugo supports that too, probably along with others I know nothing about (orgmode users may find better to use ox-hugo, which exports orgmode to Hugo-tailored Markdown, but I'm not doing this so far). These source files typically start with a header ("front matter") that defines variables (aka metadata) that help Hugo decide how to process the file and supply information like date or title for the page. This header basically consists in key, value pairs and is written in toml, yaml, json (like the configuration files). I don't really know any of these, but I use toml which is quite intuitive and easy to pick up from the examples. In the case of .org files, it is also possible to use a syntax similar to org properties.

In principle, every file in content/ becomes a page, but I do not understand all the rules about the process. What I can say is that content/_index.md (or content/_index.org, in all cases I've tried, .org files work just as well as .md files, I will not mention this equivalence again) produces your home, or landing, page. But for this to happen, Hugo needs an appropriate template (or layout) file in layouts/. For the home page, the appropriate layout is index.html. You don't get a page if there is no layout. And, probably because the home page is special, the index.html turns out to be processed even if there is no _index.md.

I've succeeded in getting a home page using the following two files:

A content/_index.org file:

#+Title: "My nice home page"

* I write in Emacs

And this is my nice =_index.org= file.

and a layouts/index.html file:

<header>  The page title is  {{ .Title }} </header>

And more layout stuff...

{{ .Content }}

As you see, the layout file is written in a mixture of html and stuff between braces that calls in content (and executes functions). To quote the documentation, "Hugo uses Go’s html/template and text/template libraries as the basis for the templating." This is all I know, but it has proved enough to make small tweaks to the layouts provided by the themes.

Now let's try a regular static page. Create a normalpage.org file in content/ with a single-line content. To produce a page from this file, again Hugo needs a template (if you run hugo at this point, Hugo will complain found no layout file for "HTML" for kind "page"). There are certain rules to match a source file to a template, and they depend on the kind of the page. I haven't completely figured out the rules to establish the kind of the page. Apparently, content/_index.md is a kind of it's own. The other pages in content/ (but not in subdirectories, see below) are, at least by default, of kind "page", and the corresponding layout is layouts/_default/single.html. Putting in this file just

{{ .Content }}

I got the page to be served at localhost:1313/normalpage/. Now I can link to it from the home page:

 a [[./normalpage][nice]] link

I found I could get Hugo to tell me what kind it is assigning to pages by adding this line to each layout:

My kind is {{ .Kind }}

This turned out to be very useful; this way I learned that the home page (_index.org) is of kind home, and confirmed that /content/normalpage.org is of kind page.

As a next level I add a subdirectory /content/blog and again a one-liner something.org file. Now Hugo complains about lack of layout for kinds "section" and "taxonomyTerm". The page http://localhost:1313/blog/ appears empty, although http://localhost:1313/blog/something displays my one-liner (which turns out to also be of kind page).

Adding an index.org page in content/blog makes content appear on blog/ (of kind page, i.e. using our single.html template). If instead you name the file _index.org, then it is interpreted as of kind section, and processed with a list.html layout. Normally this layout is written so that it displays a nice table of contents, or summary page, of all the files contained in the directory (which will be in any case translated to pages, but now they are automatically linked to). I don't know how to write a template like this, but I don't need to since I will be using a theme anyway (you can see an example in Sara's post). Apparently, if you include neither index.md nor _index.md, Hugo assumes an empty _index.md anyway (as in the case for the home page) and tries to read the list.html template, which would explain why it complained about the lack of a section layout when I put only a single file in blog.

Up to now I have placed the layouts under _default, but if you put layouts in layouts/blog, then Hugo will use those instead when reading content/blog. I also succeeded in using the blog/ layouts from another subdirectory by setting

#+type: blog

in the front matter.

Armed with this basic knowledge (admittedly sketchy and incomplete), I felt confident enough to finally download a theme and start tweaking it to my taste.

Installing a theme

I decided to try the Academia Hugo theme:

hugo new site personal
cd personal
git init .
cd themes/
git submodule add https://github.com/themefisher/academia-hugo.git

Themes come with their set of layouts and static stuff that gives the site the final appearance. Hugo can get the layouts directly from the theme directory instead of using your layouts/ directory. To use the theme you need to point Hugo to it from the configuration parameters. These are written as toml, yaml or json files, either in the single file config.toml or in several files in config/_default. I follow the second approach.

mkdir -p config/_default
mv config.toml config/_default/

Adding the line

theme = "academia-hugo"

makes Hugo aware of the theme.

Academia Hugo "hijacks" the home page (and this caused me a lot of confusion at first): its home layout (i.e. layouts/index.htm) ignores any content you care to write in _index.md and instead includes a widget_page.html layout which looks for content in content/home (including layout files within another is apparently called working with partials in Hugo jargon). So we must start populating this directory. But first, note that I am building a dual-language site. Hugo supports this; essentially by placing the source for each language in parallel trees in content/ (and setting some configuration variables). So I create the directories for languages:

mkdir -p content/en/home
mkdir -p content/es/home

Now copy the configuration files config.toml, languages.toml and params.toml from the theme's exampleSite directory and start changing the parameters. In particular add in config.toml,

defaultContentLanguage = "en"
hasCJKLanguage = false  # Set `true` for Chinese/Japanese/Korean languages.
defaultContentLanguageInSubdir = false
removePathAccents = true  # Workaround for https://github.com/gohugoio/hugo/issues/5687

[languages.en]
  languageCode = "en-us"
  contentDir = "content/en"

[languages.es]
  languageCode = "es-ar"
  contentDir = "content/es"

Actually the language part I put in a different file, called languages.toml, which has the advantage that you can say [en] instead of [languages.en] at the beginning of the section, because all the contents of languages.toml are interpreted as if defined inside a [languages] section.

Now place an empty index.md in each language home/. At this point hugo serve -D displays a site with a header and footer but otherwise empty.

Populating the home page

The home (or landing) page is made from "widgets", which can be copied and tweaked from the theme's example site. Many widgets completely ignore any content and work only from the front matter, taking custom data from various parameter files. I try to avoid this approach, which resembles too much a CMS, but I accepted it for the about.md widget. I copied it from the example site to each of the home/ directories. To work, this widget requires data stored in home/authors/admin/_index.md. Copy and edit from the example site; after the front matter, text can be added in .md format that will be displayed by the widget.

I added an intro (first.md) by copying from the blank widget. Note that the weight parameter is used to select the order in which the widgets appear.

Menu

The theme comes with a menu, adding to it is done through the configuration files. You can follow the theme's example, only that for multilingual sites the definitions must go in the [languages] section (or langauges.toml file):

[[en.menu.main]]
  name = "Home"
  url = "#first"
  weight = 10

[[es.menu.main]]
  name = "Home"
  url = "#first"
  weight = 10

etc.

The teaching page

I want a static "subsite" rendered from a source tree starting at /es/docencia (in the Spanish branch). I want to put each course in a separate subdirectory so that material for different courses will be cleanly organized in source. Hugo allows this, but it appears that if you put an index.md file in content/es/docencia, the subdirectories are ignored. If you put a _index.md file, or nothing, subdirectories are read, and a summary page is generated. However, I didn't like the automatic summary page and I wanted to write my own. I haven't figured out how to avoid the automatic summary page, so I ended up writing my own in es/docencia/docencia.org. This page appears at es/docencia/docencia, and the automatic summary at es/docencia. I simply avoided any links to the automatic summary, and link directly from the menu to es/docencia/docencia. The default type for these pages seems to be post, which causes the theme to output a signature at the bottom (a bit redundant, since this is a personal site). I avoided this by setting the type to page in the front matter:

#+title: Docencia
#+type: page
#+date: 2020-07-17

I could easily add PDF files with course material and link to them just placing them alongside the .org files in content/.

Blog

The blog part worked out of the box by creating entries in a posts subdirectory. You can use one file per post, or one directory per post (useful I think to attach images and such). In the last case, the name of the directory becomes the link, and you write in index.org. The summary is handled automatically. Of course, a link in the menu or somewhere else is needed for readers to find the blog.

Conclusion

There are still things to do to get the site I want (like adding a publication list created from my BibTex files, something I have yet to figure out). But I've succeeded in setting up a decent site, and after the initial difficulties, producing and publishing content with Hugo is proving a good experience.

comments powered by Disqus