BLOG // May 5, 2021

Pagination with Strapi and Gatsby

If you are using a Strapi backend for your Gatsby site you might notice a lot of the pagination guides are not well suited for you. But fear not, I got you!

Install the requirements

First up, install the pagination plugin we'll be using:

npm i gatsby-awesome-pagination

You won't be including this as a plugin in gatsby-config.js, so just leave that as it is!

gatsby-node.js

Edit your gatsby-node.js file and add this line to the top:

const { paginate } = require('gatsby-awesome-pagination');

Then you should have something like this in the file if you are using Strapi:

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions

  // Get our articles
  const getArticles = makeRequest(
    graphql,
    `
    {
      allStrapiArticles{
        edges {
          node {
            id
            slug
          }
        }
      }
    }
    `
  ).then(result => {
    // Create pages for each article.
    result.data.allStrapiArticles.edges.forEach(({ node }) => {
      createPage({
        path: `/blog/${node.slug}`,
        component: path.resolve(`src/templates/article.js`),
        context: {
          id: node.id,
          slug: node.slug,
        },
      })
    })
  })

  // Query for articles nodes to use in creating pages.
  return getArticles
}

We're going to add an additional function after the createPage call, so that it looks like this:

const path = require(`path`)
const { paginate } = require('gatsby-awesome-pagination');

const makeRequest = (graphql, request) =>
  new Promise((resolve, reject) => {
    resolve(
      graphql(request).then(result => {
        if (result.errors) {
          reject(result.errors)
        }

        return result
      })
    )
  })

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions

  // Get our articles
  const getArticles = makeRequest(
    graphql,
    `
    {
      allStrapiArticles {
        edges {
          node {
            id
            slug
          }
        }
      }
    }
    `
  ).then(result => {
    // Create pages for each article.
    result.data.allStrapiArticles.edges.forEach(({ node }) => {
      createPage({
        path: `/blog/${node.slug}`,
        component: path.resolve(`src/templates/article.js`),
        context: {
          id: node.id,
          slug: node.slug,
        },
      })
    })

    // Create pagination
    paginate({
      createPage, 
      items: result.data.allStrapiArticles.edges, 
      itemsPerPage: 5, 
      pathPrefix: '/archives', 
      component: path.resolve('src/templates/blog-archive.js')
    })
  })

  // Query for articles nodes to use in creating pages.
  return getArticles
}

As you can see we've added the paginate call above. Note that it uses the blog-archive.js template we'll create next.

blog-archive.js

Below is my entire blog-archive.js file. There are custom components in it and it depends on TailwindCSS, however, you can easily see from this that you are essentially reproducing your blog listing page.

import * as React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
import ArticlePreview from "../components/blog/articlePreview"
import Seo from "../components/seo"
import Pager from "../components/pager"

const BlogArchive  = ({ data, pageContext, location }) => (
  <Layout>
    <Seo
      title="Blog, News, and Tips For Software Devs"
      description="My official blog, development tips, projects and business work."
      canonical="https://blakey.co/blog"
    />

    <div className="bg-white pt-16 pb-20 px-4 sm:px-6 lg:pt-16 lg:pb-28 lg:px-8">
      <div className="relative max-w-lg mx-auto divide-y-2 divide-gray-200 lg:max-w-7xl">
        <div className="pt-10">
          <h1 className="bigTitle">Blog Archive</h1>
        </div>

        <div className="mt-20 grid gap-16">
          {data.allStrapiArticles.edges.map(document => (
            <ArticlePreview key={document.node.id} node={document.node} />
          ))}
        </div>
      </div>
    </div>

    <div className="bg-gray-50 px-8 py-10 border-t border-gray-200 -mb-10">
      <div className="relative max-w-lg mx-auto divide-y-2 divide-gray-200 lg:max-w-7xl">
          <div className="max-w-4xl">
            <Pager pageContext={pageContext} />    
          </div>
      </div>
    </div>
  </Layout>
)

export default BlogArchive;

export const pageQuery = graphql`
  query($skip: Int!, $limit: Int!) {
    allStrapiArticles (
      skip: $skip,
      limit: $limit
      sort: { fields: [created_at], order: DESC }
    ) {
      edges {
        node {
          id
          created_at(formatString: "LL")
          title
          excerpt
          slug
          body
        }
      }
    }
  }
`

The main difference vs your default listing are the skip and limit fields in the query. And we've included Pager which we'll create next.

components/pager.js

Again my whole file is attached here, this is in charge of the next page, previous page message at the bottom of your archive.

import React from 'react';
import { Link } from 'gatsby';

const Pager = ({ pageContext }) => {
  console.log(pageContext);
  const { previousPagePath, nextPagePath } = pageContext;
  return (
    <nav style={{ display: 'flex', justifyContent: 'space-between' }}>
      <div>
        {previousPagePath && (
          <Link className="readStoryLink" to={previousPagePath}>
            <button>← Newer Posts</button>
          </Link>
        )}
      </div>

      <div style={{ justifySelf: 'flex-end' }}>
        {nextPagePath && (
          <Link className="readStoryLink" to={nextPagePath}>
            <button>Older Posts →</button>
          </Link>
        )}
      </div>
    </nav>
  );
};

export default Pager;

Final words

You can adjust the above, but when you do a new build you should now have /archives as a page, and the indexes after that (e.g. /archives/2).

Comments

Subscribe to new articles

If you enjoy my content, consider subscribing. You will only receive new blog stories, no other email.