sigit.cloud
about gallery disclaimer

GatsbyJS Pagination without Plugins

2020-05-19

Running App Example

Example of running GatsbyJS blog with pagination https://master.d2c2u44of9gyth.amplifyapp.com/. This App front-end is based on GatsbyJS/React components. The pagination done by customizing gatsby-node.js , blog-list.js, and the blog-post.js.

To me, this approach is easier to implement and at the same time, provides good coding practice.

There are two parts of the pagination:

Blog Post pagination (let's call it BPP)

Blog Post pagination displays next and previous post relatives to the post that currently being viewed. See below example:

Screen Shot 2020-05-19 at 1.38.31 PM

Blog Listing pagination (let's call it BLP)

Blog Listing pagination displays how many pages in total. Provides page listing as well as next and previous page links. See below example:

Screen Shot 2020-05-19 at 1.38.13 PM

Code

gatsby-node.js

As usual, Gatsby will generate static pages during build, we can control the pages by using createPages() method inside gatsby-node.js. Below snippet taken from https://github.com/sigitp-git/sigit.cloud-gatsby/blob/master/gatsby-node.js .

//...lines removed...
// Dynamically reate pages based on graphql query on slugs from each node, put component of that page from blog-post.js template
// Create pagination using src/templates/blog-list.js
exports.createPages = ({ graphql, actions }) => {
    const { createPage } = actions
    const blogPostTemplate = path.resolve('./src/templates/blog-post.js')
    const blogListTemplate = path.resolve('./src/templates/blog-list.js')

    // this graphql is function string to pass graphql query, this is a node version of graphql
    // this query returns a promise of slugs. use then instead of async await
    return graphql(`
    query loadSlugQuery ($limit: Int!){
        allMarkdownRemark(
            sort: { fields: [frontmatter___date], order: DESC }
            limit: $limit
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        title
                    }
                }
            }
        }
    }
    `, { limit: 1000}).then(result => {
        const posts = result.data.allMarkdownRemark.edges
        posts.forEach((post, index) => {
            // create prev and next on each posts render (for Blog Post Pagination, BPP)
            const previous = index === posts.length - 1 ? null : posts[index + 1].node
            const next = index === 0 ? null : posts[index - 1].node

            // previous and next are objects props sent as pageContect object to blogPostTemplate
            createPage({
                path: post.node.fields.slug,
                component: blogPostTemplate,
                context: {
                    slug: post.node.fields.slug,
                    previous,
                    next,
                },
            })
        })
        // Create blog list pages (for Blog List Pagination, BLP)
        // Assign path /2, /3, p/4, etc
        const postsPerPage = 6
        const numPages = Math.ceil(posts.length / postsPerPage)

        Array.from({ length: numPages }).forEach((_, i) => {
            createPage({
                path: i === 0 ? `/` : `${i + 1}`,
                component: blogListTemplate,
                context: {
                    limit: postsPerPage,
                    skip: i * postsPerPage,
                    numPages,
                    currentPage: i + 1
                },
            });
        });
    })
}

Note that previous and next are node objects (blog post objects) passed as pageContext to blog-post.js during render. We will utilize these two objects on our blog-post.js to create links to previous and next post.

Also note that numPages , limit, skip, and currentPage are objects (blog list objects) passed as pageContext to blog-list.js during render. We will utilize these objects on our blog-list.js to create links to blog listing pages.

blog-post.js

We will use the Blog Post Pagination inside blog post page, below snippet taken from https://github.com/sigitp-git/sigit.cloud-gatsby/blob/master/src/templates/blog-post.js .

On this file, pageContext contains previous and next object passed by gatsby-node.js. We will use these objects to create links to previous and next post.

const BlogPost = ({ data, pageContext }) => {
    //console.log(pageContext)
    const { previous, next } = pageContext
    
      //...lines removed...
    
    return (

      //...lines removed...
      
                    <ul
                style={{
                    display: `flex`,
                    flexWrap: `wrap`,
                    justifyContent: `space-between`,
                    listStyle: `none`,
                    padding: 0,
                }}
            >
                <li>
                    {previous && (
                        <Link to={previous.fields.slug} rel="prev">
                            {"<<"+previous.frontmatter.title}
                        </Link>
                    )}
                </li>
                <li>
                    {next && (
                        <Link to={next.fields.slug} rel="next">
                            {next.frontmatter.title+">>"}
                        </Link>
                    )}
                </li>
            </ul>
blog-list.js

We will use the Blog List Pagination inside blog listing page, below snippet taken from https://github.com/sigitp-git/sigit.cloud-gatsby/blob/master/src/templates/blog-list.js .

On this file, pageContext contains numPages , limit, skip, and currentPage passed by gatsby-node.js. We will use these objects to create links to blog listing pages.

const BlogList = ({ data, pageContext}) => {
      //console.log(pageContext)
        const { numPages, limit, skip, currentPage } = pageContext
    
      //...lines removed...
    
    return (
      
      //...lines removed...
      
      <div>
      <ul
        style={{
          display: 'flex',
          flexWrap: 'wrap',
          justifyContent: 'center',
          alignItems: 'center',
          listStyle: 'none',
          padding: 0,
        }}
      >
        {!isFirst && (
          <Link to={prevPage} rel="prev" style={{marginTop: '0.1rem', marginBottom: '0.1rem', padding: '0.5rem', color: 'var(--themeColor)'}}>
            {"<< Prev"}
          </Link>
        )}
        {Array.from({ length: numPages }, (_, i) => (
          <li
            key={`pagination-number${i + 1}`}
            style={{
              margin: 0,
            }}
          >
            <Link
              to={`/${i === 0 ? '' : i + 1}`}
              style={{
                marginTop: '0.1rem',
                marginBottom: '0.1rem',
                padding: '0.5rem',
                textDecoration: 'none',
                color: i + 1 === currentPage ? '#ffffff' : 'var(--themeColor)',
                background: i + 1 === currentPage ? 'var(--themeColor)' : '',
              }}
            >
              {i + 1}
            </Link>
          </li>
        ))}
        {!isLast && (
          <Link to={nextPage} rel="next" style={{ marginTop: '0.1rem', marginBottom: '0.1rem', padding: '0.5rem', color: 'var(--themeColor)' }}>
            {"Next >>"}
          </Link>
        )}
      </ul>
      </div>

Summary

GatsbyJS pagination can be done with plugins, but also can be done with simple code on gatsby-node.js , blog-list.js, and the blog-post.js.

References:

https://www.gatsbyjs.org/docs/adding-pagination/

https://nickymeuleman.netlify.app/blog/gatsby-pagination

https://github.com/NickyMeuleman/gatsby-paginated-blog

Discuss on Dev.to

https://dev.to/sigitp/gatsbyjs-pagination-without-plugins-f1k