Divide post list by year in Hugo

One of the first things I missed about my current Hugo theme was that the default post lists were displaying everything in a simple list. Sure, there were pages with a configurable number of posts (set to 10), but there was no differentiation between years. I’m sure this works great for some, but for me with a blog dating back over 10 years (though not fully imported yet), I knew I needed something a little different.

Why list content by year

My situation is a bit unique. Though not fully imported, I have posts dating back to 2008. There are also large gaps when I didn’t post. Given this, a continual list as was the default would show posts from years apart right next to each other, which seemed confusing to me. Instead, I’d rather have my posts broken down into sections by year.

Here’s how it used to look. Not the best experience in my opinion.

Posts listed without being broken up by year
Before: All grouped together in one long list

I’ve heard of a couple of different approaches to solving this in Hugo. After a bit of research, I realized that there’s a pretty good mechanism already built in.

Listing posts by year

So let’s start with the applicable section of my layouts/posts/list.html template that I’m using for my blog posts. Note, this is very different than my image posts list template that I’ll talk about in another post (but you can see the outcome here).

{{ range (where .Site.RegularPages "Type" "in" (slice "posts")).GroupByDate "2006" }}
<h2>{{ .Key }}</h2>
<ul>
  {{ range .Pages }}
  <li>
    <span class="date">{{ .Date.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
    <a class="title" href="{{ .Params.externalLink | default .RelPermalink }}">{{ .Title }}</a>
  </li>
  {{- end -}}
</ul>
{{ end }}

So how does this work? Let’s break it down.

{{ range (where .Site.RegularPages "Type" "in" (slice "posts")).GroupByDate "2006" }}

This line is defining a range of all RegularPages. Effectively, this removes all sections and taxonomies (pages that are just lists of other pages) and returns effectively all posts. For me, this also includes all photo posts, so to exclude them, I look for posts with a type of posts, or in other words, anything in my content/posts/ directory (including all subdirectories). I’m also asking hugo to group those by date. Don’t worry too much about the 2006. Hugo uses Monday, January 2, 2006 as the base date for formatting the date string, as shown here. Here, the 2006 is saying to use the year as the key.

This is important, because we can now use that key value to print the year!

<h2>{{ .Key }}</h2>

I then use an unordered list (<ul>) to print all pages that are part of that year.

<ul>
  {{ range .Pages }}
  <li>
    <span class="date">{{ .Date.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
    <a class="title" href="{{ .Params.externalLink | default .RelPermalink }}">{{ .Title }}</a>
  </li>
  {{- end -}}
</ul>

And that’s it!

The Result

With the above code, my post list now looks cleaner and as I import more of my older posts, it will be organized in a reasonable manner. The only thing missing is that I no longer have pagination enabled because I’m not sure if it will work correctly with this method. I figure a pretty long page won’t be too bad, but if it ever becomes an issue, I can dig into how to fix it then.

Posts listed by year
After: Posts listed by year