Next.js pagination with Contentful and the GraphQL API

Published on April 23, 2021

Paginating Contentful blog posts in Next.js with the GraphQL API

In this post, we’re going to build a set of article list pages that display a number of blog post summaries per page — fetched from the Contentful GraphQL API at build time. We’ll also include navigation to next and previous pages. What’s great about this approach is that it requires no client-side state. All article-list pages are pre-rendered into static HTML at build time. This requires a lot less code than you might think!

The benefits of Static Site Generation

Next.js is a powerful framework that offers Static Site Generation (SSG) for React applications. Static Site Generation is where your website pages are pre-rendered as static files using data fetched at build time (on the server), rather than running JavaScript to build the page in the browser (on the client) or on the server at the time someone visits your website (run time).

Some of the benefits of SSG:

  • Speed. Entire pages are loaded at first request rather than having to wait for client-side requests to fetch the data required.

  • Accessibility. Pages load without JavaScript.

  • Convenience. Host the files on your static hoster of choice (Netlify, Vercel or even good old GitHub pages) and call it a day!

  • It’s scalable, fast and secure.

Here’s how it looks in a complete Next.js starter. Click here to view a live demo of the code referenced in this post.

blog index screenshot

To build the article list pagination, we’re going to harness the power of Static Site Generation provided by Next.js via the following two asynchronous functions:

  • getStaticProps: Fetch data at build time

  • getStaticPaths: Specify dynamic routes to pre-render pages based on data

If you’re new to Next.js, check out the documentation on Static Generation here.

Getting set up

I created a Next.js + Contentful blog starter repository that contains the completed code for statically generated article list pages described in this post. If you’d like to explore the code before getting started with the tutorial, you can fork the repository on GitHub here.

We’re going to create a fresh Next.js application and build up the functionality to understand how it all fits together.

For the purposes of this tutorial, you don’t need a Contentful account or any of your own blog posts. We’re going to connect to an example Contentful space that contains all of the data we need to build the article list pages and pagination. That being said, if you have an existing Contentful account and blog posts, you can connect your new Next.js application to your Contentful space with your own space ID and Contentful Delivery API access token. Just be sure to use the correct content type fields in your GraphQL queries if they are different to the example.

To spin up a new Next.js application, run the following command in your terminal:

This command creates a new directory that includes all code to get started. This is what you should see after you run the command in your terminal window. (I’ve truncated the output a little with ‘...’ but what you’re looking for is ✨ Done!)

terminal output

Navigate to the root of your project directory to view the files created for you.

If this is what you see, you’re good to go!

ls la output

You now have a fresh Next.js application with all dependencies installed. But what data are we going to use to build the article list pages?

Retrieving example data

I created an example Contentful space that provides the data for the Next.js Contentful Blog Starter. It contains the content model we need and three blog posts so we can build out the pagination.

In the root of your project directory, create an .env.local file.

Copy and paste the following into the .env.local file:

These credentials will connect the application to the example Contentful space to provide you with some data to build out the functionality.

We will use the following fields on the blogPost content type in our GraphQL queries to build the paginated article list:

  • Date (date & time)

  • Title (short text)

  • Slug (short text)

  • Tags (short text, list)

  • Excerpt (long text, presented in a markdown editor)

basic content type fields

You’re good to go if you have:

  • a fresh Next.js application

  • an .env.local file with the example credentials provided above

To run the application, navigate to the root of your project directory and run:

You’ll need to stop and start your development server each time you add a new file to the application.

So, we’ve got a Next.js application and credentials that we can use to connect us to a Contentful space! What files do we need in our application to implement a paginated blog?

Building the routes

We’re going to pre-render the following routes at build time, which will call out to the Contentful GraphQL API to get the data for each article list page:

  • /blog

  • /blog/page/2

  • /blog/page/3

  • etc

In the pages directory, create a new directory and name it blog. Add a file called index.js — this will be the /blog route.

Next, inside the blog directory, create a new directory and name it page. Create a new file inside that directory, and name it [page].js — this will be our dynamic route, which will build the routes /blog/page/{pageNumber}. Read more about dynamic routes on the Next.js docs.

Here’s what your file and folder structure should look like:

folder structure

That's all it takes to set up the routes /blog/ and /blog/page/{pageNumber}, but they're not doing anything yet. Let's get some data from Contentful.

Setting up the calls to the Contentful GraphQL API

To fill the pages with data, we need to make API calls. I prefer to define API calls in a dedicated file, so that they can be reused easily across the application. In this example, I created a ContentfulApi.js class, which can be found in the utils directory of the starter repository. We need to make two requests to the API to build our article list pages. 

Create a utils directory at the root of your project, and create a new file named ContentfulApi.js

contentful api file

Before we start building the needed GraphQL queries, let’s set up an asynchronous call to the Contentful GraphQL API that takes in a string parameter named query. We’ll use this twice later to request data from Contentful.

If you’d like to learn more about GraphQL, check out Stefan Judis’s free GraphQL course on YouTube.

To explore the GraphQL queries in this post using the Contentful GraphiQL playground, navigate to the following URL and paste any of the queries below into the explorer (without the const and =). The space ID and access token in the URL will connect you to the same Contentful space that you connected to via the .env.local file.

Add the following code to /utils/ContentfulApi.js

We’ve got our API call set up! Now, let’s fetch some data.

Querying the total number of posts

In order to calculate how many dynamic page routes we need to build and statically generate on /blog/page/[page].js, we need to work out how many blog posts we have, and divide that by the number of posts we want to show on each page.

numberOfPages = totalNumberOfPosts / howManyPostsToDisplayOnEachPage

For this, it’s useful to define how many posts you want to show on each page in a global variable or configuration object. We’ll need to use it in a few different places.

For that reason, the Next.js + Contentful blog starter contains a Config.js file in the utils directory. We’ll use the exported Config object in our API calls.

Feel free to skip this step and use a hard-coded number if you’re just exploring.

In the same ContentfulApi class, let’s create a new asynchronous method that will query and return the total blog number of blog posts.

We’ve successfully retrieved our total number of blog posts. What’s next?

Querying the post summaries by page number

Let’s create a final asynchronous method that requests the number of blog post summaries we defined in Config.pagination.pageSize, by page number.

We also request the total number of blog posts in this query. We’ll need this later, and it saves us having to make two API calls when generating the /blog route.

Here’s the code. 

Observe that we are querying for the five fields referenced at the top of this post: date, title, slug, tags and excerpt — plus the sys.id. This will be useful when rendering our data to the DOM.

The skip parameter in the GraphQL query is what does all the magic for us here. We calculate the skip parameter for the query based on the incoming page number parameter. For example, if we want to fetch the posts for page two, the skip parameter would be calculated as 1 x Config.pagination.pageSize, therefore skipping the results of page one.

If we want to fetch the posts for page six, the skip parameter would be calculated as 5 x Config.pagination.pageSize, and so on. When all your code is set up in your application, play around with the Config.pagination.pageSize to see this magic in action.

We’ve now set up all the API calls we need to get our data to pre-render our blog page routes at build time. Let’s fetch our data for page one on /blog.

Building the blog index with getStaticProps

The blog index will be available on /blog and will serve page one of our blog post summaries. For this reason, we can safely hardcode the number “1” in this file. This is great for readability — think self-documenting code!

Let’s pre-render this page at build time by exporting an async function called getStaticProps. Read more about getStaticProps on the Next.js documentation.

Add the following code to pages/blog/index.js.

We’re using getStaticProps() to:

  • Request the post summaries for page one and total number of posts from the API.

  • Calculate the total number of pages based on the number of posts and Config.pagination.pageSize.

  • Return the postSummaries.items, totalPages, and currentPage as props to the BlogIndex component.

Bonus content!

You’ll notice that the file imports from the utils directory in this example are imported using absolute paths via a module alias using @. This is a really neat way to avoid long relative path imports (../../../../..) in your Next.js application, which increases code readability.

You can define module aliases in a jsconfig.json file at the root of your project. Here’s the jsconfig.json file used in the Next.js Contentful blog starter:

Read more on the official documentation.

We’re going to be creating a components directory later on in this post, so I recommend adding this jsconfig.json file to your project to make file imports super easy. Make sure you stop and start your development server after adding this new file to enable Next.js to pick up the changes.

So that’s fetching data for page one done! But how do we build the dynamic routes at build time based on how many blog posts we have, and how many posts we want to show per page?

Building the dynamic article list pages with getStaticPaths

The article list pages will be available on /blog/page/{pageNumber} starting with the second page (/blog/ is page one). This is where we need to use getStaticPaths() to define a list of paths that will be rendered to HTML at build time.The rendered paths are based on the total number of blog posts, and how many posts we want to show per page. 

Let’s tell Next.js which paths we want to statically render by exporting an async function called getStaticPaths. Read more about getStaticPaths on the Next.js documentation.

Add the following code to pages/blog/page/[page].js:

We’re using getStaticPaths() to:

  • Request the total number of posts from the Contentful API.

  • Calculate the total number of pages we need to build depending on the page size we defined.

  • Build a paths array that starts from page two (blog/page/2) and ends at the total number of pages we calculated.

  • Return the paths array to getStaticProps so that for each path, Next.js will request the data for the dynamic page number — params.page at build time.

  • We’re using fallback: false because we always want to statically generate these paths at build time. If we add more blog posts which changes the number of pages we need to render, we’d want to build the site again. This is usually done with webhooks that Contentful sends to your hosting platform of choice each time you publish a new change. Read more about the fallback key here.

In our dynamic route, we’re using getStaticProps() in a similar way to /blog, with the only difference being that we’re using params.page in the calls to the Contentful API instead of hardcoding page number “1.”

Now we have our blog post summary data from Contentful, requested at build time and passed to our blog index and dynamic blog pages. Great! Let’s build a component to display our posts on the front end.

Building the post list component

Let’s build a PostList component that we will use on the blog index and our dynamic routes.

Create a components directory at the route of your project, create a new directory inside that called PostList, and add a new file inside that directory called index.js.

postlist screenshot

The PostList renders an ordered list (<ol>) of article elements that display the date, title, tags and excerpt of the post via the JavaScript map() function. We use next/link to enable a client-side transition to the blog post itself. Also notice that we’re using the post.sys.id on the <li> element to ensure each element in the map has a unique key. Read more about keys in React.

This example uses react-markdown to render the markdown of the excerpt field. This package is an optional dependency. Using it depends on the amount of flexibility you require for displaying formatted text in the blog-post excerpt. If you’re curious, you can view the ReactMarkdownRenderers.js file in the example project repository. This is used to add CSS classes and formatting to the markdown returned from the API. 

If you’d like to use react-markdown with the renderer options provided in the example project, install the package via npm following the given instructions.

I’ve also included a couple of date-formatting functions for the HTML <time> element referenced below in this file on GitHub to help you out.

Render your postList in your BlogIndex and BlogIndexPage components like so. Pass the totalPages and currentPage props in, too, as we’ll be using them in the final part of this guide.

You should now have your post list rendering on /blog and /blog/page/2. There’s one more piece to the puzzle! Let’s build a component to navigate back and forth in our pagination.

Building the Next.js pagination component

We’re going to make our lives really easy here! To ensure that our application can scale nicely and that we don’t have to battle with displaying or truncating a million page numbers when we’ve written a bazillion blog posts, we will be rendering only three UI elements inside our pagination component:

  • A “previous page” link

  • A current page / total pages indicator

  • A “next page” link

Inside components/PostList, add a new directory called Pagination. Inside that directory, add a new file called index.js.

pagination screenshot

Add the following code to index.js.

We’re using the next/link component to make use of client-side routing, and we’re calculating the links to the next and previous pages based on the currentPage prop.

Import the Pagination component at the top of the PostListfile, and add it at the end of the template rendering the HTML. Pass in the totalPages and currentPages props.

Next, calculate the nextDisabled and prevDisabled variables based on the currentPage and totalPages:

  • If we’re on the page one, prevDisabled = true

  • If we’re on the last page, nextDisabled = true

Finally, pass these two props to the Pagination component.

And that’s it! You’ve built statically generated article list pages based on the number of blog posts in the example Contentful space and how many posts you’d like to show per article list page. 

The finished product

In this tutorial we built statically generated article list pagination using data from Contentful in a fresh Next.js application. You can find the final styled result here, and here’s how it looks.

blog index screenshot (1)

If you want to look at how the demo site is styled with CSS, take a look at these files on GitHub.

If you’ve set up a webhook inside Contentful to trigger a build every time you publish a change, your article list pages will be rebuilt, and continue to generate the /blog/page/{pageNumber} routes dynamically based on how many blog post entries you have!

If you’ve found this guide useful, I’d love for you to come and say hi on Twitch, where I code live three times a week. I built this code on stream! And remember: build stuff, learn things, and love what you do.

Subscribe for updates

Build better digital experiences with Contentful updates direct to your inbox.

Salma Alam-Naylor

Salma Alam-Naylor

Senior Developer Advocate, Sentry

I help developers build stuff, learn things and love what they do. I code live on Twitch. I'm a teacher, musician, and a front end fanatic with a fierce mission to promote accessibility and inclusivity in technology on planet earth.

Related articles

Design tokens are a critical element of every successful design system.
Guides

Design tokens explained (and how to build a design token system)

May 16, 2024

In this tutorial you'll learn how to create a time series chart to display tag usage from blog posts published on your site, using Astro and Contentful.
Guides

Simple data visualization using Astro and Contentful

February 6, 2024

Websites still play a key role in meeting customer expectations. Read on for tips to modernize your brand’s web strategies and deliver great experiences.
Guides

Modernize your web strategy and deliver customer-winning experiences

January 11, 2024

Contentful Logo 2.5 Dark

Ready to start building?

Put everything you learned into action. Create and publish your content with Contentful — no credit card required.

Get started