Adding pagination in Gatsby
A page that displays your posts can get incredibly long as you continue to add posts. Pagination can offer a solution to having a massive list of posts, where it takes an age to scroll to the end of them.
Pagination allows you to break up those posts into multiple, smaller pages.
This tutorial was inspired by the post by Nicky Meuleman, I, however, had issues with it and couldn't get it to work. So this is my version of that tutorial.
But first, why did I choose pagination over Infinite Scroll? I ended up picking pagination solely for the challenge of creating it. After reading the article UX: Infinite Scrolling vs. Pagination I decided I made the right choice. Infinite scroll IMO is better suited to apps like Instagram.
Create a page for your posts.
Create a new file in src/templates/
this will serve as a blueprint for every page that lists the posts. Make sure to import GraphQL and Link from Gatsby, and I have also imported my Layout and Head components too.
// src/templates/post-list.js
import React, { Component } from 'react'
import { Link, graphql } from 'gatsby'
import Layout from '../components/layout'
import Head from '../components/head'
const PostList = () => {
return (
<Layout>
<Head title="Posts">
// - Posts Here
</Layout>
)
}
export default PostList
Then you will need to get the post data inside the template using GraphQL below is the query I used. To learn more about GraphQL, you can watch the following videos.
// src/templates/post-list.js
export const query = graphql`
query($skip: Int!, $limit: Int!) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: $limit
skip: $skip
) {
edges {
node {
fields {
slug
}
frontmatter {
title
date(formatString: "MMM DD, YYYY")
}
excerpt(pruneLength: 280)
}
}
}
}
`
It's time to populate the data into the template for this I am just going to copy my code. If you want to learn more about how it's filled using GraphQL, carry on watching the video by Andrew Mead.
I also added props
as a parameter to PostList,
enabling me to get that data into my component.
// src/templates/post-list.js
const PostList = props => {
const posts = props.data.allMarkdownRemark.edges
return (...
Read more about React components and props here
// src/templates/post-list.js
const PostList = props => {
const posts = props.data.allMarkdownRemark.edges
return (
<Layout>
<Head title="Posts" />
<div className={layoutStyles.pageHeader}>
<h2>Posts</h2>
<span>Just my ramberlings</span>
</div>
{posts.map(({ node }) => {
const title = node.frontmatter.title || node.fields.slug
return (
<div className={postPageStyles.postItem}>
<div className={postPageStyles.postItemTitle}>
<h2>{title}</h2>
<span>Posted on {node.frontmatter.date}</span>
</div>
<div>
<p>{node.excerpt}</p>
<Link to={`${node.fields.slug}`}>
<span>Continue Reading</span>
<span role="img"> 👉🏼</span>
</Link>
</div>
</div>
)
})}
</Layout>
)
}
export default PostList
Get data to those listing pages
At this point, I am guessing you already have a collection of posts, and they're displayed as a giant list. I won't go over creating a slug for your .md
files, if you don't, however, head over here to learn more.
The code below will create an amount of pages that is based on the total number of posts. . Each page will list postsPerPage
(3) posts, until there are less than postsPerPage
(3) posts left. The path for the first page is /posts
, the following pages will have a path of the form: /posts/2
, /posts/3
, etc.
This will also create a page for each post, each post will use the post.js
template which you can find on my repo.
// gatsby-node.js
module.exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild('Error while running GraphQL query.')
return
}
// Create the pages for each markdown file
const postTemplate = path.resolve('src/templates/post.js')
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
component: postTemplate,
path: `${node.fields.slug}`,
context: {
slug: node.fields.slug,
},
})
})
// PAGINATION FOR BLOG POSTS
const postsResult = await graphql(
`
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
}
}
}
}
`
)
if (postsResult.errors) {
reporter.panicOnBuild('Error while running GraphQL query.')
return
}
// Create blog-list pages
const posts = postsResult.data.allMarkdownRemark.edges
const postsPerPage = 3
const numPages = Math.ceil(posts.length / postsPerPage)
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/posts' : `/posts/${i + 1}`,
component: path.resolve('./src/templates/post-list.js'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
}
Adding previous/next navigation
This is where I ran to a dead end when using Nicky's tutorial. I was stuck here.
As this was my first React project, I was unaware you were unable to use const within a class in React
I kept on getting an undefined error on this.props.pageContext
after much headache, lots of dog walks, and two days later. I found the solution!! 🎉
I found it watching this video. It was a simple case of using const { currentPage, numPages } = props.pageContext
.
Now my component looked like the below. With everything working now, you can use currentPage
and numPages
to determine the routes to the previous/next page. They also make it possible to only show those links if they exist.
// src/templates/post-list.js
const PostList = props => {
const { currentPage, numPages } = props.pageContext
const isFirst = currentPage === 1
const isLast = currentPage === numPages
const prevPage =
currentPage - 1 === 1
? 'posts/'
: 'posts/' + (currentPage - 1).toString()
const nextPage = 'posts/' + (currentPage + 1).toString()
const posts = props.data.allMarkdownRemark.edges
return (
<Layout>
{!isFirst && (
<Link to={prevPage} rel="prev">
← Previous Page
</Link>
)}
{!isLast && (
<Link to={nextPage} rel="next">
Next Page →
</Link>
)}
</Layout>
)
}
Adding numbering
Iterate over numPages
and output a number with the relevant link.
{
Array.from({ length: numPages }, (_, i) => (
<Link key={`pagination-number${i + 1}`} to={`/${i === 0 ? '' : i + 1}`}>
{i + 1}
</Link>
))
}
I ended up placing all the pagination into it's own div enabling me to style it. This ended up looking like the below:
<div className={paginationStyles.paginationBlock}>
<div className={paginationStyles.previous}>
{!isFirst && (
<Link to={prevPage} rel="prev">
<span role="img" className={paginationStyles.emojis}>
👈🏼
</span>
</Link>
)}
</div>
<div className={paginationStyles.numbers}>
{Array.from({ length: numPages }, (_, i) => (
<Link
key={`pagination-number${i + 1}`}
to={`posts/${i === 0 ? '' : i + 1}`}
>
{i + 1}
</Link>
))}
</div>
<div className={paginationStyles.next}>
{!isLast && (
<Link to={nextPage} rel="next">
<span role="img" className={paginationStyles.emojis}>
👉🏼
</span>
</Link>
)}
</div>
</div>
If this doesnt make any sense, I mean I am also new to Gatsby and React you can check out the relevant files to get the pagination working below:
The whole repo is also here.
Update:
I actually got in touch with Nicky over Twitter to advise him of the bug in his tutorial. This has now been resolved. However, i'll leave this blog post as it is, as a leaning curve.