Tutorial

Pagination in Gatsby Using gatsby-awesome-pagination

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Despite Gatsby’s amazing performance, it would be best not to force the user to load every single post onto the same page. Instead, we’ll explore the gatsby-awesome-pagination plugin to segment our post archive into more manageable sections.

Installation

We’ll start with the gatsby-starter-blog starter since it comes with markdown support.

$ gatsby new pagination-example https://github.com/gatsbyjs/gatsby-starter-blog
$ yarn add gatsby-awesome-pagination

Paginate

In our gatsby-node.js file, Let’s import the paginate method and pass it an object below the query for our posts. It only needs a few things, namely the createPage action, an array of our posts, the item limit per page, the slug for our new archive page, and its template. Now when we make this query we’ll get get a bunch of extra stuff in the pageContext prop we’ll use to manage which page is rendered.

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

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

  const blogPost = path.resolve(`./src/templates/blog-post.js`)
  const result = await graphql(` ... `)

  paginate({
    createPage,
    items: result.data.allMarkdownRemark.edges,
    itemsPerPage: 3,
    pathPrefix: '/posts',
    component: path.resolve('src/templates/blog-archive.js')
  });
};

Template

The gatsby-awesome-pagination plugin is, as far as I know, just for paginating separate ‘archive’ pages. So instead of adding pagination to the starter’s home page we’ll link to a separate posts page with everything. Groups of posts on normal pages would probably be best with just a static query and something like a carousel to manage what’s shown.

index.js
import React from "react";
import { Link, graphql } from "gatsby";
import Bio from "../components/bio";
import Layout from "../components/layout";

class BlogIndex extends React.Component {
  render() {
    const { data } = this.props;

    return (
      <Layout location={this.props.location}>
        <Bio />
        <Link to='/posts'><button>See All Posts</button></Link>
      </Layout>
    )
  }
};

export default BlogIndex;

Our archive template already has access to our query’s pageContext in its props, which we’ll pass to a separate Pager component. Note that we need to use a normal query instead of a static query since our pageContext will be passing values into the skip and limit arguments, which we’ll also need to set.

blog-archive.js
import Pager from '../components/pager';

export const pageQuery = graphql`
  query($skip: Int!, $limit: Int!) {
    site { ... }
    allMarkdownRemark(
        sort: { fields: [frontmatter___date], order: DESC},
        skip: $skip,
        limit: $limit
        ) {
      edges { ... }
    }
  }
`;

const BlogArchive = ({ data, pageContext, location }) => {
    const posts = data.allMarkdownRemark.edges;

    return (
      <Layout location={location}>
        {posts.map(({ node }) => {
            const title = node.frontmatter.title || node.fields.slug
            return (
              <article key={node.fields.slug}>
                <header>
                  <h3>
                    <Link to={node.fields.slug}> {title} </Link>
                  </h3>
                  <small>{node.frontmatter.date}</small>
                </header>
                <section>
                  <p dangerouslySetInnerHTML={{ __html: node.frontmatter.description || node.excerpt }} />
                </section>
              </article>
            )
        })}

        <Pager pageContext={pageContext} />
      </Layout>
    )
};

export default BlogArchive;

Pager

The final step is really as simple as getting the page paths from the pageContext. If you log the pageContext you can see the skip and limit properties that are being passed to the query, along with pageNumber and numberOfPages which you could use to generate a number and link for each page.

components/pager.js
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 to={previousPagePath}>
            <button>← Newer Posts</button>
          </Link>
        )}
      </div>

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

export default Pager;

Posts

Adding navigation between each post is just as simple and doesn’t even need a plugin. This particular starter actually already supports this, but let’s pretend it doesn’t for now.

Where we create our pages from our query, we just need to add two fields that will then have access to in the pageContext. If it’s the first post then prev shouldn’t exist, else we’ll return the last post. Same with next, only return the next post if one exists.

gatsby-node.js
posts.forEach((post, index) => {
  createPage({
    path: post.node.fields.slug,
    component: blogPost,
    context: {
      slug: post.node.fields.slug,
      prev: index === 0 ? null : posts[index - 1].node,
      next: index === (posts.length - 1) ? null : posts[index + 1].node
    },
  })
});

It’s the same execution as with our Pager, check if prev/next exist, throw them in a Link, and you’re done.

blog-post.js
const BlogPostTemplate = ({ data, pageContext, location }) => {
  const post = data.markdownRemark;
  const { prev, next } = pageContext;

  return (
    <Layout location={location}>
      <Link to='/posts'>Archive</Link>
      <article>
        <header>
          <h1> {post.frontmatter.title} </h1>
          <p> {post.frontmatter.date} </p>
        </header>
        <section dangerouslySetInnerHTML={{ __html: post.html }} />
        <hr />
      </article>

      <nav style={{ display: 'flex', justifyContent: 'space-between' }}>
        <div>
          {prev && <Link to={prev.fields.slug} rel="prev"> ← Last Post </Link>}
        </div>

        <div style={{ justifySelf: 'flex-end' }}>
          {next && <Link to={next.fields.slug} rel="next"> Next Post → </Link>}
        </div>
      </nav>
    </Layout>
  )
};

export default BlogPostTemplate;

Closing Thoughts

This plugin is still being ironed out, but I’m excited to see how they’ll improve this already incredibly simple design.

Creative Commons License