By default, Hugo doesn’t create date-based archive pages for you. And while it’s relatively straightforward to create an archive page that lists all the posts you’ve ever made(external link) (which is fine if you’re an infrequent blogger), it’s a fair bit trickier to make individual pages for each year or month of posts, as platforms like Wordpress create by default.

Since this serves as a microblog as well as a long-form blog, I knew when I shifted to Hugo that I wanted month-based archives. The easiest way that I found to do this is to leverage Hugo’s taxonomy system, creating taxonomies for months and year. My starting point was the information in this thread in the Hugo forums(external link), but I’ve had to build on that and do some things differently to make it all work as I wanted. At any rate, my starting point to get the taxonomies existing was to specify this in my config.toml:

[taxonomies]
	tag = "tags"
	category = "categories"
	year = "year"
	month = "month"

At the start of every post (or really, any content that I want to appear on my archives page, including interactions) I have front matter specifying the month and year, like this:1

year: 2021
month: 2021-04

Lest you worry that it’ll get really annoying manually typing that out every time you want to make a new post, actually I’ve set up my archetypes to generate that front matter for me, right under “date”:

date: {{ .Date }}
year: {{ .Date | dateFormat "2006" }}
month: {{ .Date | dateFormat "2006-01" }}

Like this, you will get list pages (the same view as on your homepage, or for a tag or category by default) at yoursite.com/year/2021/ and yoursite.com/month/2021-01/ (replace the trailing slash with .html if you’ve enabled “ugly URLs” like me). But that’s not what I wanted. I had already customised my posts’ URLs, so rather than going www.jayeless.net/posts/example-post.html they appear at www.jayeless.net/2021/01/example-post.html. What I wanted was month and year pages to match – so if someone just lopped example-post.html off the end of a URL, they’d get all my posts from that month, and similarly for year. Thankfully, in Hugo this is possible too, although it takes a bit more work.

What I needed to do was define a URL manually for each year and month. You can do this by defining custom metadata for each taxonomy term(external link), which you do within the same /content/ folder that all your actual content goes into. Under /content/ you create subdirectories for month/ and year/, and under those, further subdirectories for each month and year (e.g. month/2021-01/,2 year/2021/), and in each of those you need an _index.md file. (Note the underscore; it’s important to include it, or this won’t work.)

So for example, my /content/month/2021-01/_index.md file says:

---
title: "January 2021"
url: "/2021/01/"
---

Unfortunately, you do need to do this manually for every month/year, but the good news is that you can create them pre-emptively – pages shouldn’t be generated or get linked to until you actually have posts with that month or year. So it’s not like this is a job you need to remember to do the first time you post each month – you can do a stack of six months’ worth or whatever whenever it crosses your mind.

Anyway, so far so good! Like this, you’ll get a list page (a similar view to what you might have on your blog’s home page, or a category page) for each month or year, with a title of e.g. “January 2021” instead of “2021-01” for the months, paginated if that’s how your list pages usually work. At this point, you’ll probably want to create a “head” archives page, to link to all your newly-generated month and year pages (along with your tag and category pages, if you have those).

What I’ve done is create an archives.md file under /content/, but it’s really a dummy, all it contains is this (the “layout” part being most important):

---
title: "Archives"
layout: archives
---

Here you'll find all my posts gathered together by date, category and tag, for more focused browsing. You might also want to see [my interactions](/interactions/).

To actually provide all the taxonomy links (for time period, categories and tags) I have a /layout/_default/archives.html file. Mine (now)3 looks like this:

{{ define "main" }}
<h1>Archives</h1>
{{ .Content }}

<h2>By Date</h2>
<ul class="archives-list">
	{{ range $name, $taxonomy := .Site.Taxonomies.month }} {{- $cnt := .Count -}}
	  {{- with $.Site.GetPage (printf "%s" $name) -}}
          <li><a href="{{ .RelPermalink }}" title="All posts from {{ .Title }}">{{ .Title }}</a> ({{$cnt}})</li>
	  {{ end }}
	{{- end -}}
</ul>

<h2>By Category</h2>
<ul class="archives-list">
	{{ range $name, $taxonomy := .Site.Taxonomies.categories }} {{- $cnt := .Count -}}
	  {{- with $.Site.GetPage (printf "/categories/%s" $name) -}}
          <li><a href="{{ .RelPermalink }}" title="All posts in category &lsquo;{{ .Title }}&rsquo;">{{ .Title }}</a> ({{$cnt}})</li>
	  {{ end }}
	{{- end -}}
</ul>

<h2>By Tag</h2>
<ul class="archives-list">
	{{ range $name, $taxonomy := .Site.Taxonomies.tags }} {{- $cnt := .Count -}}
	  {{- with $.Site.GetPage (printf "/tags/%s" $name) -}}
          <li><a href="{{ .RelPermalink }}" title="All posts with tag &lsquo;{{ .Title }}&rsquo;">{{ .Title }}</a> ({{$cnt}})</li>
	  {{ end }}
	{{- end -}}
</ul>

{{ end }}

And put together, you can see what my archives page now looks like. If your posting frequency is such that “year” makes more sense here than “month”, you can do that just by changing the single word month in the code to year. Anyway, at this point – if you don’t mind your “month” and “year” pages having the same “list” view as your blog homepage – you can stop. But I wasn’t quite satisfied with this.

See, returning to the very first guide I linked here, I actually liked the “list every single post” style of archive page. I just didn’t want every single post ever to all be listed on the one page. I wanted the lists to be broken up by month (and year while I was at it, but primarily months). To do this, I needed to customise /layouts/year/list.html and /layouts/month/list.html. To show you what I changed, I’d like to compare my /layouts/categories/list.html and /layouts/month/list.html:

{{ define "main" }}
<h1>Posts categorised &lsquo;{{ .Title }}&rsquo;</h1>
<div class="h-feed">
{{ range .Paginator.Pages }}
{{ .Render "summary" }}
{{ end }}
<div style="clear: both;"></div>
{{ partial "pagination.html" . }}
{{ partial "h-card.html" . }}
</div>
{{ end }}
{{ define "main" }}
<h1>Posts from {{ .Title }}</h1>
<div class="h-feed">
<ul id="time-archive">
{{ range .Pages.Reverse }}
{{ .Render "li" }}
{{ end }}
</ul>
<div style="clear: both;"></div>
{{ partial "h-card.html" . }}
</div>
{{ end }}

That is, I made three main changes:

  1. Told it to render the “li” view instead of “summary” (and added <ul> tags around the list items).
  2. Removed the pagination (I want everything on a single page).
  3. Told it to reverse the order of the pages (because the default is reverse-chronological, and for this I want chronological).

Then, obviously, I needed to make sure I have an “li” view. A simplified version of my /layouts/posts/li.html, stripped of anything that’s really extraneous to this guide, is as follows:

<li class="h-entry">
	<a href="{{ .RelPermalink }}" class="u-url">
		<time datetime="{{ .Date.Format "2006-01-02 15:04:05 -0700" }}" class="dt-published">{{ .Date.Format "2006-01-02" }}</time>:
		{{ if .Title }}<span class="p-name">{{ .Title }}</span>:{{ end }}
		<span class="p-summary">{{ .Summary | truncate 120 }}</span>
	</a>
</li>

And, well, you can see what my January 2021 archive looks like for yourself. It has some additional emojis and “micro post”, “photo post”, etc. prefacing the posts I haven’t given titles, but otherwise it looks a lot like the code snippet above.

So there you have it! This is the method I’ve used to create monthly archives for my blog in Hugo. It might seem like a lot of effort for something that other blogging platforms handle by default, but it works, and isn’t too much ongoing effort once set up.


  1. Note the use of a hyphen as the separator instead of a forward slash for the month; this is to prevent a later step breaking. ↩︎

  2. This is the thing that breaks if you use a forward slash as the separator. ↩︎

  3. Previously I manually added links as new months and years started, having a top-level list for years and second-level lists for the months within each year. In contrast, the method I’ve provided here will keep the archives page updated automatically as new posts appear for each month (just like it updates the tags and categories), but it skips the year-level pages. At some point I had found a method to list all the years and months you had posts dating to (not necessarily that were in the taxonomies!), but I have a big backlog of book reviews that I hadn’t given the year/month taxonomies and thus I had a lot of broken links with that method. ↩︎