How to Create a Hugo Theme
Introduction
This site is created with Hugo and also uses a custom theme that I built. I was using another theme, but I wasn’t a huge fan of how it was set up. For example, running the hugo
command should trigger the production
path, but in this case it didn’t because the theme was using a nonstandard variable in hugo.toml
to control that. That’s not ideal since hugo.toml
gets checked into version control. I also prefer using the CLI with the -e
argument to control the environment rather than set an environment variable, but that had no effect with the theme.
It also seemed to make arbitrary decisions, like adding a robots
meta with noindex,nofollow
to each page if it wasn’t built for production, so my site wasn’t getting indexed by search engines, and I didn’t realize it.
Rather than go find another theme that would probably have its own quirks, I decided to make my own. At least this way, I will be aware of everything that’s going on, and I’ll be able to add onto it as needed.
My Goals
- Keep it simple and only change the defaults where needed.
- Add Google Analytics for production.
- Customize meta info.
Add your theme
In your Hugo project:
hugo new theme name
Theme content
When you first make your theme, it will give you some default content in themes\name\content
. If you already have content for your site, you won’t need this, however you may need to override some of it.
For example, I had to add the following content to my blog in order to override the ones that came with the theme:
hugo new content/_index.md
hugo new content/posts/_index.md
After that you can just set the post-1.md
, post-2.md
, and post-3/index.md
in your theme to draft status to hide them.
Customize <head>
Support for Google Analytics
Luckily, Hugo already has an internal template for Google Analytics.
{{ template "_internal/google_analytics.html" . }}
However, it won’t render anything unless you have included your analytics ID in hugo.toml
.
[services]
[services.googleAnalytics]
ID = 'G-XXXXXXXXXX'
We also only want to render it for production, so we can surround it with a condition.
{{ if eq hugo.Environment "production" }}
{{ template "_internal/google_analytics.html" . }}
{{ end }}
Google recommends putting it immediately after the <head>
element, so paste it at the top of themes\name\layouts\partials\head.html
.
Now when we build our site with the hugo
command, which sets the environment to production
by default, it will add the analytics scripts.
If you don’t want the scripts, you can specify any other environment, e.g.:
hugo -e testing
Since hugo server
serves a development
environment by default, you won’t have to worry about it affecting your analytics.
Meta information
You’ll probably want to add some custom meta info to the <head>
, such as a description and keywords. Google hasn’t cared about the keywords
meta since 2009, but other search engines may still use it. Google still respects description
, though. I added this to <head>
:
{{ if .Description }}
<meta name="description" content="{{ .Description }}" />
{{ end }}
{{ if .Keywords }}
<meta name="keywords" content='{{ delimit .Keywords "," }}' />
{{ end }}
{{ delimit .Keywords "," }}
takes an array and outputs a comma-separated string.
So if you populate the built-in description
or keywords
variables in the front matter of your posts, the meta info will be added to the head automatically.
Add author to posts
I also wanted to add my name to the posts, so I added the following code to themes\name\layouts\_default\single.html
:
{{ if .Params.author }}
<p id="author">By <span>{{ .Params.author }}</span></p>
{{ end }}
So if the author
property exists in the front matter, the author will be displayed.
Custom partial template
At the end of my blog posts I have a message linking to my t-shirt store. This was done with a partial template so that I don’t have to link to it manually in the content of every article.
To do this, I added support.html
to themes\name\layouts\partials\
with the following content:
<p>Support message goes here.</p>
However, if you scroll down, you’ll see a different message. That is because the above content is just the default for the theme itself and can be customized for your individual Hugo project by adding another support.html
file to layouts\partials
. If a partial with the same name exists in your project as in the theme, it will use the one from your project instead.
Favicon
The default theme ships with a favicon.ico
, but rather than use that, let’s add support for PNG.
Add this to head.html
:
<link rel="icon" type="image/png" href='{{ printf "%s%s" site.BaseURL "favicon.png" }}' />
Now all you have to do is drop a favicon.png
file into the static
folder. Keep in mind your baseURL
in hugo.toml
should be set. If this file doesn’t exist, a 404 will be returned.
If you want your icon to show up in Google search results, they require a multiple of 48, so at least 48x48px.
You can also specify different sizes and special rel
values for Apple icons.
Styles
At this point it’s just about styling the site how you want in themes\name\assets\css\main.css
. You can also add IDs where you need them. For example, the tags at the bottom are in a bullet-pointed list by default. This list is created in themes\name\layouts\partials\terms.html
. In order to style them differently from other ul
elements, I gave the first div
an ID of taxonomy
:
<div id="taxonomy">
<div>{{ $label }}:</div>
<ul>
{{- range . }}
<li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
{{- end }}
</ul>
</div>
Conclusion
I also added RSS compatibility. But as you can see, making your own theme doesn’t have to be complicated.
Support the blog! Buy a t-shirt or a mug!