DEV Community

Steven Mercatante
Steven Mercatante

Posted on • Updated on • Originally published at stevenmercatante.com

Add Previous and Next Article Links in Gatsby


This post was originally published at stevemerc.com


Yo! You might want to check out this article first on Managing Draft Articles in Gatsby.

Lots of sites include "Previous Article" and "Next Article" buttons/links on the bottom of their articles. I wanted some, too!

Updating the UI

Let's start with the fun stuff first - updating the UI. I've got a template that renders the actual markdown content, and I want to add the previous and next buttons to it:

// templates/article.js

class ArticleTemplate extends React.Component {
  // These represent the previous and next articles...
  // ... we'll pass these via GraphQL later on
  const { previous, next } = this.props.pageContext

  // Boolean letting us know if we should show prev and next links
  const needsPagination = Boolean(previous) || Boolean(next)

  render() {
    // Imagine the article's body is rendered here...

    {needsPagination && (
      <Pagination>
        {previous && (
          <Link to={previous.fields.slug}>
            <span>← {previous.frontmatter.title}</span>
          </Link>
        )}

        {next && (
          <Link to={next.fields.slug}>
            <span>{next.frontmatter.title} β†’</span>
          </Link>
        )}
      </Pagination>
    )}
  }
}

(Pagination is a styled component - here's the source if you're interested.)

Refresh your site and you'll see... nothing new! We haven't passed the previous and next articles via GraphQL yet, so needsPagination will be false and that conditional won't render anything. Let's fix that.

Updating The Content Pipeline

Note: I've made some major edits to my gatsby-node.js, but the general idea translates to whatever you've got.

I'm also excluding all the logic that's not related to previous and next articles.

// gatsby-node.js

function createArticlePages() {
    return new Promise((resolve, reject) => {
      const articleTemplate = path.resolve(`./src/templates/article.js`)

      resolve(
        query.then(result => {
          const articles = result.data.allMdx.edges

          /**
           * We only want to check published articles when
           * fetching the `previous` and `next` ones. So,
           * let's convert the `articles` array into an object
           * whose keys are the article slugs.
           *
           * You don't need this step if you're not setting a
           * `published` flag on articles.
           */
          const publishedArticles = articles
            .filter(article => {
              return article.node.frontmatter.published === true
            })
            .reduce((acc, article) => {
              acc[article.node.fields.slug] = article
              return acc
            }, {})

          articles.forEach((article, index) => {
            const [previous, next] = getPrevAndNextArticles(
              publishedArticles,
              article.node.fields.slug
            )

            createPage({
              path: article.node.fields.slug,
              component: blogArticleTemplate,
              context: {
                slug: article.node.fields.slug,
                previous: previous ? previous.node : null,
                next: next ? next.node : null,
              },
            })
          })
        )
      }
    )
}

Most of this is standard Gatsby stuff, but I'm taking an extra step to only fetch the previous and next articles from the subset of published articles (you can skip this step if you don't have a published flag for your articles).

If you're wondering why I'm only using publishedArticles to fetch the previous and next articles, and not using it instead of articles in the forEach below, it's because I want to create article pages regardless of their published status - I just don't want to link to unpublished ones.

Near the bottom of the example is where we pass the previous and next articles via Gatsby's context object, making them available to our Article template.

Let's take a look at the getPrevAndNextArticles function:

function getPrevAndNextArticles(articles, slug) {
  const currentArticleIndex = Object.keys(articles).findIndex(
    slug => slug === slug
  )
  const articlesArray = Object.values(articles)
  let prevArticle
  let nextArticle

  if (currentArticleIndex < articlesArray.length - 1) {
    prevArticle = articlesArray[currentArticleIndex + 1]
  }

  if (currentArticleIndex > 0) {
    nextArticle = articlesArray[currentArticleIndex - 1]
  }

  return [prevArticle, nextArticle]
}

This function takes an object of articles (where each key is the article's slug) and the slug of the article we're currently iterating over in the forEach loop of createArticlePages. We need to find the index of the current article in relation to its siblings, and then we can return the previous and next articles. If the current article is either the first or the last one in the collection, previous or next will be null, respectively.

Now when you refresh your site, you should see previous and next article links (assuming you have enough published articles!)

πŸ‘‹ Enjoyed this post?

Join my newsletter and follow me on Twitter @mercatante for more content like this.

Top comments (0)