Creating Year/Month Route to Index Template

I recently posted this question about setting up monthly archive index pages. The answer was very helpful for one aspect of what I’m trying to do, but I’m still struggling with setting up the route/collection for how I want it to work.

My primary goal is to have a route (or collection?) for a primary tag called “links” such that:

  • a URLs of the form yields an index page for all posts with the primary tag “links” made during February of 2020.

I’m really struggling to figure out how to make this happen. This question from 2018 alludes tantalizingly to the solution, but like the asker there, I don’t understand how to read or handle the :year and :month tokens that I’m using in routes.yaml.

I think I need to construct a filter in either routes.yaml or the {{get}} statement in links.hbs, but if so, I don’t see how to pass :year and :month as parameters to the filter.

For reference, here’s my routes.yaml file as it stands:

  / : index
  /archive/: archive
    permalink: /links/{year}/{month}/{slug}/
    filter: tag:links
    template: links

  tag: /tag/{slug}/
  author: /author/{slug}/

After a couple more days of muddling around, I’ve landed on the solution of pre-defining routes for every year/month I’m trying to index posts for, which I don’t like very much:

  / : index
  /archive/: archive
    controller: channel
    permalink: /away/2020/01/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-01-01 00:00:00'+published_at:<'2020-02-01 00:00:00'
    controller: channel
    permalink: /away/2020/02/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-02-01 00:00:00'+published_at:<'2020-03-01 00:00:00'
    controller: channel
    permalink: /away/2020/03/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-03-01 00:00:00'+published_at:<'2020-04-01 00:00:00'
    controller: channel
    permalink: /away/2020/04/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-04-01 00:00:00'+published_at:<'2020-05-01 00:00:00'
    controller: channel
    permalink: /away/2020/05/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-05-01 00:00:00'+published_at:<'2020-06-01 00:00:00'
    controller: channel
    permalink: /away/2020/06/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-06-01 00:00:00'+published_at:<'2020-07-01 00:00:00'
    controller: channel
    permalink: /away/2020/07/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-07-01 00:00:00'+published_at:<'2020-08-01 00:00:00'
    controller: channel
    permalink: /away/2020/08/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-08-01 00:00:00'+published_at:<'2020-09-01 00:00:00'
    controller: channel
    permalink: /away/2020/09/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-09-01 00:00:00'+published_at:<'2020-10-01 00:00:00'
    controller: channel
    permalink: /away/2020/10/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-10-01 00:00:00'+published_at:<'2020-11-01 00:00:00'
    controller: channel
    permalink: /away/2020/11/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-11-01 00:00:00'+published_at:<'2020-12-01 00:00:00'
    controller: channel
    permalink: /away/2020/12/{slug}/    
    template: away
    filter: primary_tag:links+published_at:>='2020-12-01 00:00:00'+published_at:<'2021-12-01 00:00:00'
    permalink: /away/{year}/{month}/{slug}/
    filter: primary_tag:links
    template: away

Here are the reasons this solution isn’t ideal:

  • I don’t understand why the controller: channel line is necessary in the routes entries, but it seems to be.
  • Since I’m essentially brute-forcing the routes, I don’t think there’s any way to reference the current/next/previous months within the template I’m using, so there’s no straightforward way to include links to those index pages.
  • Since the routes are pre-defined ahead of time, if someone goes to the URL for a future month, instead of getting a 404, they get the template rendered without any data (because no posts match the filter query).

Given all of these downsides, I think I may have to concede defeat and use a paginated collection. I don’t like the fact that with pagination, the content at, e.g., /page/03/ isn’t fixed, but the clumsiness of having to manually define routes to get the URL convention I want is too much hassle.

I still think I must be missing something, here—surely cases like mine are what the :year and {year}, etc. syntax in routes.yaml is meant to address, but I just don’t understand the underlying structures well enough to see through to the solution.

If anybody has any insight here, I would still very much appreciate it!

This is a fairly complex use case, but I think you were already half way there with the solution posted in the other topic. Rather than making a lot of routes in your site you could make use of anchor links, by adding an id to each of the post date headings like so:

{{#foreach posts}}
  <article class="post post-date-{{date format="M"}}">
    <div class="post-label" id="{{date format="YYYY"}}-{{date format="MM"}}">{{date format="MMMM YYYY"}}</div>
    <a class="post-link" href="{{url}}">
      <h2 class="post-title">{{title}}</h2>

Then to create a list of links to each month you could use some JavaScript:

// Get all the date heading dates
let dates = [...document.querySelectorAll(".post-label[id]")].map(
  date =>

// Remove duplicates
dates = Array.from(new Set(dates));

// Render list of links that go to the date section
document.querySelector(".navigation").innerHTML = dates
  .map(date => `<a href="#${date}">${date}</a>`)
  .join(" ");

The JavaScript would find all unique instances of each date and produce a link that goes to #2020-02 for example. Using this technique would mean extending the limit so the posts wouldn’t get cut off by pagination. I made a CodePen to demo how it would work:

This seems more manageable in my eyes, but would like to hear your thoughts :slight_smile:

A bunch of things came up such that I had to pause work on this little project, but I’m back at it now, and just wanted to say thank you, AGAIN, for this idea. I admit that what I’m trying to do is a bit “swimming upstream” from the assumptions Ghost makes, and it’s therefore very appreciated to have my idiosyncratic use case considered by people who are much more knowledgeable than me.

I think this just about gets me to where I want to be–thank you again, David, for the working example! You went above and beyond. :+1:


Thanks for the kind words, you’re very welcome! This is exactly what the community forum is for :heart_decoration:

1 Like