Making Ghost's #prev_post and #next_post helpers work better

Does your theme need next and previous post buttons with a little more context awareness?

Making Ghost's #prev_post and #next_post helpers work better

You're reading your favorite blog and come to the end of the article. Where do the "next" and "previous" buttons come from, if your theme has them?

Meet the {{#prev_post}} and {{#next_post}} helpers. You can read their official documentation for the basics, but in general, they just grab the previous or next post anywhere on the site, based on chronological order.

These guys are basically contexts. Put {{#prev_post}} {{/prev_post}} within an existing {{#post}} {{/post}} , and you've got access to the post published before your current post, including all the helpers like {{title}}, {{url}}, and so forth.

This great if it makes sense for readers to read all your posts in chronological order. It's less awesome if you have several different serialized novels running at once, and want your prev/next buttons to go to the next post in the series. Or if you have multiple collections and want to keep your browsing readers within a collection.

It's possible to make these helpers a teeny bit better. They'll take an in argument, like this:

	{{#prev_post in="primary_tag"}}
		<a href="{{url}}">{{title}}</a>
	{{/prev_post}}

This lets you move forward and backwards through posts with the same primary_tag. Can that solve the serialized novel problem? Yes, maybe, if you planned your tags right. Unfortunately, the only arguments that in takes are primary_tag and primary_author – these helpers just don't understand any sort of complex routing.

I got into trouble today doing some work for a client who has some posts that are excluded from her regular post index. I had already put them on a separate route, but then they were still offered as previous and next posts at the bottom of her regular posts. Not what she wanted. Here's my workaround:

{{#next_post}}
	{{^has tag="#no-archive"}}
    	<div id="prev-next" class="is-newer">
    		<h3>{{t "Newer"}}</h3>
    		<h4><a href="{{url}}">{{title}}</a></h4>
    	</div>
	{{else}}
		{{#get "posts" limit="1" filter="tag:-#no-archive+published_at:>{{published_at}}" order="published_at asc"}}
    		{{#foreach posts}}
    			<div id="prev-next" class="is-newer">
        			<h3>{{t "Newer"}}</h3>
        			<h4><a href="{{url}}">{{title}}</a></h4>
                </div>
    		{{/foreach}}
		{{/get}}
	{{/has}}
{{/next_post}}

See what I did there? I'm checking if the next post is tagged #no-archive, using the {{#has}} helper. If it isn't, I link it, using the {{url}} and {{title}} helpers. If the next post is tagged #no-archive, then I instead #get a post that is newer than that post, filtering to exclude #no-archive posts and to get the first post that's published later.

Tip: {{published_at}} refers to the post the {{#next_post}} context found. I could have used the original post's publication date, but nested contexts in filters get messy, and anyway, I already know that the first post found is #no-archive, if that's the part of the logic I'm in. I'm getting the first one after that which doesn't have the #no-archive tag.

Reverse the logic for {{#prev_post}}, and it works great.

Do you publish in multiple languages? A similar strategy could be used to get posts only with the right language tag (regardless of where in the tag order it is).

Happy post browsing!


Hey, before you go... If your finances allow you to keep this tea-drinking ghost and the freelancer behind her supplied with our hot beverage of choice, we'd both appreciate it!


Aside: It's interesting that the prev and next helpers don't know what collection they're in, given that individual posts can be in a single collection. I haven't gotten to poke at the routing part of the Ghost core, so there's probably a good reason? 🤷‍♀️