🌟 Help others discover us, star our GitHub repo! 🌟

How to Implement Pagination with Webiny Headless CMS in React

Taminoturoko BriggsTwitter
July 20, 2022

Fetching data and rendering it in a UI built with React is fantastic … until your blog starts to run into tens, or even hundreds of articles. Once it does, you’ll notice your API calls will slow down to a crawl, and rendering will start to become unresponsive. Let’s look at how you can eliminate these issues using Pagination. What is pagination? How do you fetch data using pagination with Apollo Client in a React application? These questions will be answered as we dive into building a paginated blog with React and Webiny Headless CMS.


To follow along with this tutorial, you need to have the following:

Introduction to Webiny Headless CMS

A headless CMS is one that deals strictly with the content. It is a backend-only CMS where the content repository is separated from the presentation layer. Once created its content is served via an API giving front-end developers full control over the user experience using tools they are most familiar with. The Webiny Headless CMS is GraphQL based, with a powerful content modeling feature and it’s serverless, so optimized for scalability by default. To learn more about headless CMS checkout our headless CMS guide.

What Is Pagination

Pagination is the method of splitting digital content into different pages where site visitors can navigate between these pages by clicking on links often located at the bottom of a page.

This pattern is used so site visitors do not get overwhelmed by a mass of data on one page which would make it difficult for identifying a target item. By splitting large data sets into chunks, it can also help solve performance issues both on the client and server side.

There are two strategies mainly used to achieve pagination and they are:

  • Offset-based pagination
  • Cursor-based pagination

In this article, we are only going to cover the cursor-based pagination since it’s the one implemented on Webiny headless CMS.

Cursor-Based Pagination

Cursor-based pagination works by returning a pointer (cursor) to the last items on a page which will then be used on subsequent requests to get the next result after the given pointer. The returned cursor must be unique and sequential for all items in the dataset.

Let’s look at an example of how cursor-based pagination works using ID as the cursor. Say we have the following datasets:

[ { id: 1, author: 'author1'}, { id: 2, author: 'author2'}, { id: 3, author: 'author3'}, { id: 4, author: 'author4'}, { id: 5, author: 'author5'}, { id: 6, author: 'author6'}, { id: 7, author: 'author7'}, { id: 8, author: 'author8'}, ]

To get only four results for every page the first request will look like this:

cursor: 0 limit: 4 // OR after: 0 limit: 4

Which will return four (4) results starting with an ID of one (1)

[ { id: 1, author: 'author1'}, { id: 2, author: 'author2'}, { id: 3, author: 'author3'}, { id: 4, author: 'author4'}, ]

The next request will be:

cursor: 4 limit: 4 // OR after: 4 limit: 4

With results:

[ { id: 5, author: 'author5'}, { id: 6, author: 'author6'}, { id: 7, author: 'author7'}, { id: 8, author: 'author8'}, ]

IDs are often used as the cursor because they are sequential and unique. However when objects can be sorted or filtered in some way, APIs are better suited to using a different method. This is why Webiny's cursors are located in a separate property. We will see how that works shortly.

Create and Deploy a New Webiny Project

To get started, we need to first create a Webiny project, deploy it, and then model our content in the headless CMS using the generated Admin app.

To deploy a new Webiny project, enter the following command in the terminal:

npx create-webiny-project webiny-pagination

After running the above command you will be asked several questions one of which is to choose a database, for that, select DynamoDB which is suitable for our use case. The above command will create a Webiny project called webiny-pagination which consists of three applications: a GraphQL API, an Admin app, and also a React website.

Once the new project has been created, it’s time to deploy it into our AWS account. We can do this with the following commands.

cd webiny-pagination yarn webiny deploy

If it’s our first deployment it will take over 20 minutes. After it’s done the URL to the Admin App, GraphQL API endpoints and website would be printed out in the terminal. We will use them shortly.

Defining Content Model in the Admin App

A content model defines the structure of the content we want to store in the CMS. For our use case, we will define a Book model which will hold the book entries we will use to implement pagination.

To access the Admin app we can click the URL which was printed in the terminal earlier when we deployed our project.

Deploy was sucecssful message

(Perhaps you have closed the terminal, to view the URLs you can run the yarn webiny info command.)

The first time you access the Admin app you will be be prompted to create a default user with our details. After doing that, we will be taken to the welcome page:

Webiny welcome screen

Next, navigate to the Content Models page by clicking on the New Content Model button within the Headless CMS card. On the next page click on New Modal at the top of the page and we will see a prompt with a form to add some information for the new Content Model.

New content model form

In the name input enter Book, then click on the CREATE MODEL button. On the next page, we will see different field types we can add for our Book model:

Creating fields in the content model

To add a field, we need to drag and drop it in the drop zone, then in the prompt that appears supply a Label(required) and click on the SAVE FIELD button. Below is a GIF showing how a TEXT field labeled title is added:

Now following the process in the GIF above let’s create TEXT fields with labels: authors, image, previewLink, publishDate, publisher, subtitle, title, and LONG TEXT field with a label of description. For the field with a label of authors make to turn on the Use as a list of texts switch. We are doing this so that it can hold more than one (an Array of strings).

Title field form

Below are all the fields for the Book model. After creation, click on the SAVE button at the top right of the page.

All of the model fields completed

Here is what the object of the fields we created will look like:

{ authors: ['Accomazzo Anthony', 'Murray Nathaniel'], description: "LEARN REACT TODAY The up-to-date, in-depth, complete guide to React and friends. Become a ReactJS expert today", image: "http://books.google.com/books/content?id=ppjUtAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api", previewLink: "http://books.google.com/books?id=ppjUtAEACAAJ&dq=React&hl=&cd=1&source=gbs_api", publishDate: "2017-03", publisher: "Fullstack.IO", subtitle: "The Complete Guide to ReactJS and Friends", title: "Fullstack React" }

Now that we have added fields to define the structure of contents for our Book model, we can start adding content to it. To add the object above as content to our model, in the sidebar, expand the Headless CMS tab, under the UNGROUPED section click on Books and we will see a page like this:

Book model page

Next, click on the NEW ENTRY button, and on the prompt that appears enter the value of the properties of the above object in the corresponding input like this:

Add a new entry into the content model we created

After that, click on SAVE & PUBLISH at the top-right of the page, then click CONFIRM on the pop-up that appears. With this, we have added our first content to our Book model.

To test and introspect GraphQL queries and mutations on our content models before using them from a client-side application we can use the GraphQL playground. To access the playground, click on API Playground in the sidebar of the Admin app.

Now that we have set up a Webiny project and created our Content model, we are left with consuming the GraphQL API in the front-end and using it to create the pagination functionality.

Implementing Pagination

We will start by setting up our React app, then using the GraphQL API we will fetch our book data to be used for the pagination functionality.

Setting Up React

I have already created a starter repo containing the template and data to be added to Webiny which I fetched from the Google book API. The next step is to clone the GitHub repo. We can do this with the following commands:

git clone https://github.com/webiny/write-with-webiny.git cd write-with-webiny/tutorials/pagination-react

For working with GraphQL in our frontend we will be using Apollo client, I have included @apollo/client and graphql in the dependency object of the package.json file. So by running the npm install command the needed packages will be installed.

Now when we start the app with npm start, we should see the following screen in our browser:

The frontend application running in the browser

Right now we are getting our book data from the data.js file in the src directory of the cloned app. We will be moving it over to Webiny and fetching it from there shortly.

Initialize Apollo Client

Before initializing Apollo we need to first get an API token to enable us to gain access to our GraphQL API and the correct Headless CMS GraphQL API URL.

To get the API token, head over to the Admin app, and in the sidebar expand the Settings tab and we will see API keys under the ACCESS MANAGEMENT section.

API Keys menu item

Select API keys, and on the next screen click on New API key. we will see a prompt where we can set the name, description, and access control options for our KEY.

New API Key form

Enter a name and description then scroll down the page, expand the Content tab and select All locales.

Authorising the API key for all locales

Next, expand the Headless CMS tab which contains options to control which operations the API token can, or cannot, perform.

Setting the access level for content

For our use case we only need read access to the Headless CMS GraphQL API to perform queries on the Book model so here is how we will set the controls.

Allowing read access to the API key

Allowing access to the book content model only

After setting the controls, click on the SAVE API KEY button at the bottom of the page and our API token will be created.

New token created field

Now we can copy the token and store it in a .env file in our React app.

Next, to get the URL, go to the API playground in the Admin app, click on the Headless CMS - Read API tab at the top of the page and copy the URL directly below the tab.

The GraphQL Read API interface

After copying the URL we should also store it in the .env file in our React app.

Now, to initialize Apollo client, modify index.js file in the src directory to look like this:

import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client'; const client = new ApolloClient({ uri: process.env.REACT_APP_WEBINY_GRAPHQL_URL, cache: new InMemoryCache(), headers : { Authorization : `Bearer ${process.env.REACT_APP_WEBINY_GRAPHQL_TOKEN}` } }); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <ApolloProvider client={client}> <App /> </ApolloProvider> )

Notice we have passed the URL and API token to the configuration object of ApolloClient. This will enable us to start working with our CMS in our React app.

Implement Cursor-Based Pagination

For implementing Cursor-based pagination Webiny Headless CMS GraphQL API includes a limit and after argument. limit takes an integer that indicates the number of contents with which to return in every request. after takes a string which is the cursor of the last content returned. By specifying a cursor in the after argument, the returned result will be the contents after the specified cursor.

Using the above, we need to create a query that will be able to receive a limit and after argument and also return the cursor of the last returned content.

In the App.js file, first add the following imports:

import { useQuery, gql } from '@apollo/client'; import { useEffect } from 'react';

Next, add the following query before the App component.

// src/App.js const LIST_BOOKS = gql` query GetBooks ($limit: Int, $after: String){ listBooks (limit: $limit, after: $after){ data { id authors description image previewLink publishDate publisher title subtitle } meta { cursor hasMoreItems } error { message data } } } `

Above, we have requested the data field for getting content in our Book model, meta for working with pagination, and error for handling errors.

Next, in the App component, add the following state and the useQuery hooks for sending query requests and returning the result and properties needed for working with GraphQL.

// src/App.js const [cursors, setCursors] = useState([null]) const [page, setPage] = useState(0) const {data, loading} = useQuery(LIST_BOOKS, { variables: { limit: 10, after: cursors[page] } })

With the above code when we are on the first page (page = 0) after will equal null making our request return only the first ten(10) contents. On subsequent pages (page > 0) after will be equal to the cursor needed to get the appropriate contents.

Next, lets render the data coming from our CMS and also handle errors which might occur.

// src/App.js <div className="app"> {!loading && data?.listBooks.data ? <div className='books'> {data?.listBooks.data?.map((book) => ( <Book key={book.id} title={book.title} image={book.image} authors={book.authors} publisher={book.publisher} publishDate={book.publishedDate} id={book.id} /> ))} </div> : <> {loading ? <div className='center'>Loading...</div> : ( <span className='center'> {data?.listBooks.error.message} {data?.listBooks.error.data.reason} </span> ) } </> } </div>

Now to set the cursors array state with the cursors needed to get subsequent pages, add the following lines of code after the useQuery hook:

// src/App.js useEffect(() => { const pointer = data?.listBooks.meta?.cursor if(!loading && pointer !== cursors[page]){ setCursors([...cursors, pointer]) } }, [data])

Next, let’s add the button needed to navigate between pages. Add the following lines of code before the closing tag (</>) of the div with a className of app.

// src/App.js {!loading && <div className='buttons'> <button onClick={() => {setPage(page - 1)}}>Prev</button> <button onClick={() => {setPage(page + 1)}}>Next</button> </div> }

In the above code, we have included a previous button that decreases the page state by one and a next button to increase it by one. By modifying the page state the useQuery responsible for sending our request will re-run with the after property been equal to the cursor needed to fetch the appropriate data.

Now, when we head over to our app in the browser we will see the following button for navigating between pages:

Previous and next buttons showing in the UI

Although the pagination feature works right now, we will notice that when we are on the first page, the previous button is still enabled and doesn't indicate that there is no previous page. This is the same for the next button when we are on the last page.

To fix this, modify the div with the className of buttons to look like this:

// src/App.js {!loading && <div className='buttons'> <button disabled={page === 0 && true} style={{backgroundColor: `${page === 0 ? 'gray' : '#6796ec'}`}} onClick={() => {setPage(page - 1)}}>Prev</button> <button disabled={!data?.listBooks.meta?.hasMoreItems && true} style={{backgroundColor: `${data?.listBooks.meta?.hasMoreItems ? '#6796ec' : 'gray'}`}} onClick={() => {setPage(page + 1)}}>Next</button> </div> }

With this, the buttons should now be working properly.


In this tutorial, we have covered how pagination can be implemented with Webiny headless CMS in a React app. The entire code for this tutorial is available on GitHub:

Full source code: https://github.com/webiny/write-with-webiny/tree/main/tutorials/pagination-react

This article was written by a contributor to the Write with Webiny program. Would you like to write a technical article like this and get paid to do so? Check out the Write with Webiny GitHub repo.

Find more articles on the topic of:reactbuild projectspaginationcontributed articles

About Webiny

Webiny is an open source serverless CMS that offers you all the enterprise-grade functionalities, while keeping your data within the security perimeter of your own infrastructure.

Learn More


Want to get more great articles like this one in your inbox. We only send one newsletter a week, don't spam, nor share your data with 3rd parties.

By using this website you agree to our privacy policy
Webiny Chat

Find us on Slack

Webiny Community Slack