Prismic cursor-based GraphQL pagination in Gatsby
2020-03-31
I use Prismic as a headless CMS to host all my content (including images) and fetch all of that data using a plugin in Gatsby. One of the last steps when finishing my blog was adding pagination, for some reason I had a bit of trouble getting it to work. There isn't a lot of information available online so let me share my findings so you might have an easier time. This is an update!
1. Creating pages - gatsby-node.js
Inside this file you have the opportunity to create pages and that's what I did:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage, createRedirect } = actions const result = await graphql(` { prismic { allPosts { totalCount } } } `) const totalCount = result.data.prismic.allPosts.totalCount const postsPerPage = 4 const numPages = Math.ceil(totalCount / postsPerPage) reporter.info(`generating ${numPages} blog listing page(s)`) Array.from({ length: numPages }).forEach((_, i) => { const currentPage = i + 1 const pageProperties = { path: i === 0 ? `/` : `/page/${i + 1}/`, component: path.resolve("./src/templates/BlogListTemplate.tsx"), context: { limit: postsPerPage, numPages, currentPage, }, } if (i !== 0) { pageProperties.context.cursor = btoa( `arrayconnection:${i * postsPerPage - 1}` ) } createPage(pageProperties) }) }
Let me explain what is happening in the code above:
- Fetch the totalCount of posts using a GraphQL query
- Calculate the number of pages Math.ceil(totalCount / postsPerPage), keeping in mind the desired amount of post per page.
- Build a pageProperties object. This object contains context that will be shared with the template (BlogListTemplate.tsx)
- Creates a page using the pageProperties object
You might also have noticed the cursor property that is set in the context property. This value will be used to actually fetch the correct posts in the template, but more on the later. NOTE: an extra devDependency is required to make this work
1
yarn add -D btoa
In general, we've found that cursor-based pagination is the most powerful of those designed. Especially if the cursors are opaque, either offset or ID-based pagination can be implemented using cursor-based pagination (by making the cursor the offset or the ID), and using cursors gives additional flexibility if the pagination model changes in the future. As a reminder that the cursors are opaque and that their format should not be relied upon, we suggest base64 encoding them.
- https://graphql.org/learn/pagination/#pagination-and-edges
2. Creating a template - BlogListTemplate.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
export const query = graphql` query BlogList($limit: Int!, $cursor: String) { prismic { allPosts(sortBy: date_DESC, first: $limit, after: $cursor) { edges { node { _meta { id uid } title date image excerpt } } } } } `
The query receives the $limit and $cursor parameter from the context passed by the createPage() method from gatsby-node.js.
By using allPosts(sortBy: date_DESC, first: $limit, after: $cursor) we make sure only 4 post (in our case) are returned by the GraphQL API.
The $cursor is equal to the cursor of the last document of the previous page.
3. Display page information
1 2 3 4 5 6 7 8 9 10 11 12
const BlogList: React.FC<Props> = ({ data, pageContext }: Props) => { const posts = data.prismic.allPosts.edges return ( <LayoutComponent> <BlogPosts posts={posts} /> <PageNavigation pageContext={pageContext} /> </LayoutComponent> ) } export default BlogList
Our component receives the post data but also the page context as props.
1 2 3 4 5
export interface PageContext { limit: number numPages: number currentPage: number }
You can write your own PageNavigation component as I did and use the context to show and/or hide the previous and next button, show page numbers,...
If you have any questions, do not hesitate to contact me or leave a comment below.