Next/Prev post preview link navigation with Gatsby.js

This website kushalbhalaik.xyz is made using JAMstack. So it is build on top of Ghost CMS and uses Gatsby.js for front-end and ultimately deployed via Netlify.
Although Ghost by default, provides very impressing themes; but I decided to use a simplified custom UI theme based on Gatsby.js, which also provides blazing fast static pages.
In this article we'll see how to add post preview navigation to Gatsby.js. Lets get on.

What is a post preview navigation?
Often time when you reach end of an article or a post, you see a navigation to previous or next article, which makes up for a good user experience and really keeps your visitor hooked on to what your website has to offer.
Basically, it is just option to navigate to previous or next post from the same post, without having to go back to index or catalogue page.

Let's go!
This whole thing is based on Gatsby-Starter-Ghost template for Gatsby.js.
Lets begin with cloning this template from the GitHub to your local machine:
git clone https://github.com/TryGhost/gatsby-starter-ghost.git
cd gatsby-starter-ghost
Now download all the project dependencies using NPM (which will take a minute to download):
npm install
Once all the dependencies are installed, Gatsby will be installed on your machine:
Lets see Gatsby in action all you have to do is run following command:
gatsby develop
gatsby develop creates a local server localhost:8000 to run your website

You will see following web page on your browser:

Now if you go to an article, you will see there is a primitive way to get to next page using a hyperlink, and no way at all to go back to previous post; we are gonna change that in a bit!!
The code!!
We will begin with changing how posts are created in gatsby-node.js file.
modify the existing Create post pages section with following code:
posts.forEach(({ node }, index) => {
// This part here defines, that our posts will use
// a `/:slug/` permalink.
node.url = `/${node.slug}/`
const prev = index === 0 ? false : posts[index - 1].node
const next = index === posts.length - 1 ? false : posts[index + 1].node
createPage({
path: node.url,
component: postTemplate,
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.slug,
prev,
next
},
})
here we provided get prev and next node's slug from the current node using below:
const prev = index === 0 ? false : posts[index - 1].node
const next = index === posts.length - 1 ? false : posts[index + 1].node
Once this is done, we will have to see how to add this in our UI.
For that create a component PostPreview.js in component/common directory
import { Link, graphql } from "gatsby";
import PropTypes from "prop-types";
import React from "react"
const PostPreview = ({prev = null, next = null, ...props }) => {
if (!prev && !next) {
return null
}
return (
<nav style={{ display: 'flex', justifyContent: 'space-between' }} {...props}>
<div>
{prev && <Link to={prev.slug} rel="prev"> ← Last Post {console.log("props",props)}</Link>}
</div>
<div style={{ justifySelf: 'flex-end' }}>
{next && <Link to={next.slug} rel="next"> Next Post → </Link>}
</div>
</nav>
)
}
export default PostPreview;
Now One last thing, add PostPreview.js into src/templates/post.js
...
//first import the PostPreview component
import PostPreview from "../components/common/PostPreview"
...
const Post = ({ title, data, location, pageContext }) => {
const { prev, next } = pageContext;
...
//add following line at the end of <article> tag:
...
<PostPreview prev={prev} next={next} />
</article>
...
Now save all the changes, Gatsby will automatically hot reload and update these changes.

Enhancing the Navigation
Now that we have working navigation links for next and previous post, we can now focus on beautifying the navigation links.
We will be adding following things to existing links. i.e:
- Post Titles
- Post Images
- Total reading time
So far we only had the link to next/prev posts, we don't have above fields in our data. So first we need to have these data to enhance our UI.
Let' head back to gatsby-node.js, where we will change our graphql query to get these extra items:
Before changes:

After changes:
Now download all the project dependencies using NPM (which will take a minute to download):
const path = require(`path`)
const { postsPerPage } = require(`./src/utils/siteConfig`)
const { paginate } = require(`gatsby-awesome-pagination`)
//added this to get readingTime out of post html
const { readingTime } = require(`@tryghost/helpers`)
/**
* Here is the place where Gatsby creates the URLs for all the
* posts, tags, pages and authors that we fetched from the Ghost site.
*/
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
allGhostPost(sort: { order: ASC, fields: published_at }) {
edges {
node {
slug
tags {
name
}
}
next {
slug
title
feature_image
html
}
previous{
slug
title
feature_image
html
}
}
}
allGhostTag(sort: { order: ASC, fields: name }) {
edges {
node {
Now we will change the post createPages to get these changes
modify the existing Create post pages section with following code:
// Create post pages
posts.forEach((curr, index) => {
// This part here defines, that our posts will use
// a `/:slug/` permalink.
curr.node.url = `/${curr.node.slug}/`
let prev;
let next;
if(curr.previous){
prev = curr.previous;
const prevArticleReadingTime = readingTime(prev);
prev["readingTime"] = prevArticleReadingTime;
}
else {
prev = false;
}
if(curr.next){
next = curr.next;
const nextArticleReadingTime = readingTime(next);
next["readingTime"] = nextArticleReadingTime
}
else {
next = false;
}
createPage({
path: curr.node.url,
component: postTemplate,
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: curr.node.slug,
prev,
next
},
})
})
We have removed the logic conditions of how we were fetching our next/prev link urls earlier with the ones we are now getting from graphql queries
Upgrading Navigation UI
Lets modify our PostPreview UI with better Bootstrap classes.
Lets see Gatsby in action all you have to do is run following command:
import React from "react";
import { Link } from "gatsby";
const PostPreview = ({prev, next,props}) => {
if (!prev && !next) {
return null
}
return (
<div className="container">
<section className="post-preview-navigation">
<nav style={{ display: 'flex', justifyContent: 'space-between' }}>
<div className="col-md-6 col-xs-6">
{prev && <Link to={prev.slug} rel="prev">
<span aria-hidden="true">
← Last Post
</span>
<div className="row post-item no-gutters border rounded overflow-hidden flex-md-row mb-4 ml-1 mr-1 h-md-250 position-relative">
<div className="col p-4 d-flex flex-column position-static">
<h6 className="post-preview-title mb-1">{prev.title}</h6>
<div className="post-card-footer-right">
<div className="mb-2 text-muted"><p className="lead"><span className="badge badge-secondary">{prev.readingTime}</span></p>
</div>
</div>
</div>
<div className="col-auto d-none d-lg-block p-4">
<img className="bd-placeholder-img" width="120" height="100" src= {prev.feature_image} alt=""></img>
</div>
</div>
</Link>
}
</div>
<div className="col-md-6 col-xs-6">
{next && <Link to={next.slug} rel="next">
<span aria-hidden="true" >
Next Post →
</span>
<div className="row post-item no-gutters border rounded overflow-hidden flex-md-row mb-4 ml-1 mr-1 h-md-250 position-relative">
<div className="col p-4 d-flex flex-column position-static">
<h6 className="post-preview-title mb-1">{next.title}</h6>
<div className="post-card-footer-right">
<div className="mb-2 text-muted"><p className="lead"><span className="badge badge-secondary">{next.readingTime}</span></p>
</div>
</div>
</div>
<div className="col-auto d-none d-lg-block p-4">
<img className="bd-placeholder-img" width="120" height="100" src={next.feature_image} alt=""></img>
</div>
</div>
</Link>}
</div>
</nav>
</section>
</div>
)
}
export default PostPreview;
After the hot reload you will see the following cards has been added for next/prev navigation along with more details like title, image and reading time.

Live preview of the feature:
http://blog.kushalbhalaik.xyz/jeff-weiners-ubuntu-goodbye#page