Creating a blogroll grouped by year and month

I’m trying to create a chronological archive of blog posts that looks like this:

2024

November
[Post Title] [day of month]
[Post Title] [day of month]

January
[Post Title] [day of month]
[Post Title] [day of month]

Each year should be an h3 heading, each month an h4 heading, and posts are listed underneath with their day number. The posts are in descending order (newest first).

I can get the posts to display correctly with this:

{{#get "posts" limit="all" order="published_at desc"}}
    {{#foreach posts}}
        <li><a href="{{url}}">{{title}}</a> {{date published_at format="D"}}</li>
    {{/foreach}}
{{/get}}

But when I try to add the year/month grouping logic, only the first year/month headers appear, while subsequent headers don’t show up (though the posts and their day numbers do). Here’s one of the many ways I’ve tried so far:

    {{!-- Archive/Blogroll Section --}}
    <section class="blogroll">
        <h2>Archive</h2>
        {{#get "posts" limit="all" order="published_at desc"}}
            {{#foreach posts}}
                {{#if @first}}
                    <h3>{{date published_at format="YYYY"}}</h3>
                    <h4>{{date published_at format="MMMM"}}</h4>
                    <ul>
                {{else}}
                    {{#unless (match (date published_at format="YYYY-MM") (date ../posts.[0].published_at format="YYYY-MM"))}}
                        </ul>
                        <h3>{{date published_at format="YYYY"}}</h3>
                        <h4>{{date published_at format="MMMM"}}</h4>
                        <ul>
                    {{/unless}}
                {{/if}}
                <li>
                    <a href="{{url}}">{{title}}</a> {{date published_at format="D"}}
                </li>
            {{/foreach}}
            </ul>
        {{/get}}
    </section>

I need help figuring out the correct logic to show new year/month headers whenever they change in the loop. The tricky part seems to be detecting when to insert new headers as we move through the posts. Does anyone have any ideas? Thanks.

  • What version of Ghost are you using? GhostPro latest

This is one of those spots where handlebars really doesn’t make things easy.

You’re able to see if a post’s date matches the first post, but not whatever post you saw most recently. (Wouldn’t it be cool if there were a settable/getable variable? That would be awesome.)

One possibility might be to use partials to pass in the value. [Caution: I may be sending you off on a wild goose chase.] When you hit your unless condition, do {{> somepartial date=published_at newindex=@index}} and then inside that partial, you’ll have the most recent date. I’m not sure if you can {{#foreach ../posts start=newindex}}, but if that works then you could effectively continue looping through posts and then call the partial (yeah, from within the partial) again when you trigger the unless condition. Will that work? I have zero idea. I hope you’ll report back. :slight_smile:

Another option that /does/ work is to include the year and month in a class on the each post, and to write those headers out on each post, but hide them whenever the same class came on the post before them. (It’s a little tedious to put the css together, but it’ll definitely work.)

Thanks for the reply! I will try to experiment with the partials, and think more about the CSS!

I was playing around more and realized that NQL helps me get what I want, if I hardcode it. It’s not being dynamically generated which is a stink – but it could work. The example below shows what I’m talking about – it doesn’t group by month, only year.

    <section class="blogroll">
        <h2>Archive</h2>
            {{!-- Get the most recent year --}}
            {{#get "posts" filter="published_at:<='2024-12-31'+published_at:>='2024-01-01'" order="published_at desc"}}
                <h3>2024</h3>
                {{#foreach posts}}
                    <p><a href="{{url}}">{{title}}</a> - {{date published_at format="MMMM D"}}</p>
                {{/foreach}}
            {{/get}}
    </section>