Alert: this is not a step-by-step guide on how to build a Hugo theme. It focuses on the overall picture and mental model that I built up during the process of learning. I hope this can help me pick up Hugo again quickly in the future when I need to. But I hope some of you will find it helpful, too :)
I've built a Hugo theme, yBlog, for my personal blog. Here are some take-aways.
Mental model
As a content writer, we write contents in markdown, put them in correct folders, add some meta-data in front matter, and that's it. Hugo takes care of everything else for us. But if we want to build our own themes, we need to understand a bit more about how Hugo works behind the scenes. The below diagram shows my mental model when I use Hugo:
This is perhaps an over-simplified version of Hugo's work flow. If you look at Hugo's source code, you'll find the actual implementation much more complicated. But I find this mental model helpful for getting us thinking in Hugo.
To summarise Hugo's model in one sentence: everything is a page, and every page has a corresponding model that provides all the information needed to render it. This model is really the central piece of Hugo's flow. Everything you write will become its fields, and everything you see on the rendered pages are retrieved from it. You provide data to the model by writing markdown (including front matter), and you retrieve data in the templates by accessing the model's fields and methods. Unfortunately, you won't be able to find this "model" in any documentation, because it only exists in our mental model, the real implementation is much more sophisticated. You can refer to variables in hugo, they are corresponding to the model "fields" that we are talking about here.
So, let's have a closer look at the data flow around this model, it can be separated into three steps (as shown in the diagram above):
Hugo parses the markdown contents and the front matter, as well as the config file, gathering information into models (providing information)
Models are mapped to corresponding templates (grouping information)
Templates consume the information carried in the models, rendering them in HTML (rendering information)
Providing information
Information is the raw material for building the website. They come from 3 sources: the content markdown files, the config.toml/yaml/json
file, and the Hugo engine.
Information from contents
The most notable piece of information is the markdown content itself. This content will be available as a page variable, and can be retrieved using .Content
in the page template (will explain shortly).
We are also able to provide other information that is specific to a particular page. These are added as front matter at the top of the content file. Some fields are predefined by Hugo, including title
, description
and so on. When they are defined in the front matter, they are available for use in templates via their names (eg .Title
, .Description
, ...).
Other than these predefined fields, we can also add whatever information we want to front matter. These user-defined fields will also be available in the page template. They are grouped together in the .Params
field. For example, if we add a note
field to front matter, as this is not Hugo predefiend, we can later use it via .Params.Note
in the templates.
Information from config
We'll also need some information that are website-wise, for example, the base URL of the website, the website's author, etc. They belong to the config.toml
file. When you provide these fields in the config file, they become available via the .Site
template variable. For example, baseURL = 'https://my.domain'
in config.toml
becomes .Site.BaseURL
in the templates.
Information from Hugo engine
During the building process, Hugo also adds a great amount of helpful information for us to use in templates. These include .Permalink
, .TableOfContents
, .WordCount
, .ReadingTime
and many more. Refer to the documentation of page variables and site variables to see what else are available.
Grouping information
The information gathered from the above step will be grouped into models and dispatched to templates. Basically, the templates come in two versions (Kind
s, in Hugo's term): single page templates and list templates.
Single page templates display individual contents. For example, a single markdown file that you put under the contents folder will be forwarded to a single page template.
List templates don't display the content of an individual content file; they are used to provide a summary page of a certain collection. For example, section, taxonomy, etc. For example, the blogs page of the example site of my theme is rendered by a list template. That said, you CAN create a file corresponding to a particular list template; this way, you can provide front matter to the model of a list page. see here
By default, you should see these three default template files under the layouts/_default
folder:
_default/
|- single.html
|- list.html
`- baseof.html
single.html
is the default single page template. Your individual content pages will be rendered by this template. list.html
is the default list template, you can use it to display the summary of a section. Obviously, Hugo supports many other types of templates, but those are basically special cases of either single page template or list page template. You can read more about Hugo's template lookup order here.
Note here baseof.html
is the base of all other templates. You can add the elements common to all pages here, like the head part of html. Other templates can be embedded inside this one. We'll explain more of template structuring in the next section.
Rendering information
Finally, the models go to corresponding templates, and we can retrieve the information we provided earlier in the contents and config file. To retrieve a field in model and insert it into the template, put it inside a pair of mustache symbol: {{ }}
. For example, <h1>{{ .Title }}</h1>
in the template will be rendered as <h1>[value of the model's 'title' field]</h1>
in the final output. Hugo uses Go's templating language, which is very powerful and interesting to work with. You can read about it in Hugo's documentation.
Hugo's templates can be embedded and make use of partials to reuse components. To show an example, here is the baseof.html
of my theme:
<!DOCTYPE html>
<html class="sticky" id="root">
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
{{- partial "script.html" . -}}
</body>
</html>
The {{- block "main" . }}{{- end }}
part shows how to use blocks to define the outer shell of a template. My _default/single.html
is defined as:
{{ define "main" }}
<!-- content of a single page -->
{{ end }}
The content of the single page template will be embedded into baseof.html
template in the block "main" .
part. The dot (.
) after "main"
is the context passed to embedded templates (similar to function argument, but each template only has one context). You can read more about base template and blocks here
The {{- partial "script.html" . -}}
part shows how to use partial templates. I have created a script.html
template under the partials
folder with the following content:
{{ $script := resources.Get "/js/script.js" | minify }}
<script src="{{ $script.RelPermalink }}"></script>
This piece will be inserted into the partial "script.html ."
part. This way, we can decouple parts of a template and improve maintainability.
You can read more about partial templates here.
Wrap up
Right, that's it. As said this article is not a step-by-step toturial but aims to provide a high level overview of how Hugo works. There are a lot of details I cannot cover here, but it summarises what I learned so far, and hopefully it will be useful when I leave Hugo for some time and come back one day. For detailed information on Hugo, go to the documentation, and they also have a good tutorial to get you started.
Top comments (0)