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.

Tech stack for kushalbhalaik.xyz

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.

The Post Preview Navigation provided by default in Ghost CMS's Atilla theme (example)


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
Success message of running gatsby develop on your machine

You will see following web page on your browser:

Gatsby running on localhost:8000

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
            },
        })
code changes in gatsby-node.js

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;
PostPreview.js

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>
   ...
post.js after adding PostPreview.js

Now save all the changes, Gatsby will automatically hot reload and update these changes.

Next/Prev post preview navigation changes on an article

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:

Original graphql query in gatsby-node.js

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 {
gatsby-node.js with updated graphql query and readingTime import

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
            },
        })
    })
Here we are adding all the next/prev details required for us
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;
Updated html/css for PostPreview.js

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.

Final result next/prev navigation

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

Kushal Bhalaik