Visit Agility Academy to take courses and earn certifications. It's free, and you can learn at your own pace. Learn More
Implementing Pagination with Next.js
Pagination is the process of dividing items into multiple pages or manageable chunks. When using the List endpoint in our API, the number of items to retrieve from the request using the Take parameter defaults to 10. The number of items to retrieve from the request can be increased to a maximum of 50, however, any more than this will require the use of our Skip parameter which can be used for implementing Pagination.
In this tutorial, we'll be showing you how you can implement Pagination in Next.js on a Content List with the Skip and Take parameters using a Load More button to fetch more results.

Set up Content
In this tutorial, we will be paging through a list of Contacts. Our Contact Content Model consists of the following fields:
Name => Text Field
Position => Text Field
Image => Image Field

Set up Page Module
We've set up a Page Module with a Linked Content Field that links to our Contact Content List. We will use this Page Module to display our first 4 Contacts, then dynamically load the rest.

Lets Code
Getting Initial Contacts
In the code for our Contacts Page Module (Contacts.js), we can use the getCustomInitialProps function to call the Agility API and take 4 initial contacts at build time. We will return our 4 contacts and the totalCount for our Contacts Content List, which in this case is 10.
Contacts.getCustomInitialProps = async ({
agility,
languageCode,
}) => {
const api = agility
try {
const contacts = await api.getContentList({
referenceName: 'contacts',
languageCode,
take: 4,
sort: 'properties.itemOrder',
direction: api.types.SortDirections.ASC
})
return {
contacts: contacts.items,
totalCount: contacts.totalCount
}
} catch (err) {
console.log(err)
}
}Updating Contacts Component
Next, let's add some code for our React Component. We can destructure customData from the component's props to further destructure contacts and totalCount that we returned from our getCustomInitialProps function.
We can map over our contacts to display our initial results.
import React, { useState } from 'react'
import { AgilityImage } from "@agility/nextjs"
const Contacts = ({ customData }) => {
const { contacts, totalCount } = customData;
const [skip, setSkip] = useState(4)
const [take, setTake] = useState(4);
const [loading, setLoading] = useState(false)
const [loadedContacts, setLoadedContacts] = useState([])
const loadMoreContacts = async () => {
setLoading(true)
setTimeout(() => {
setLoading(false)
}, 500)
}
return (
<div className="max-w-screen-xl mx-auto">
<div className="grid grid-cols-4 gap-8">
{contacts.map((contact, index) => (
<div key={index}>
<div className='relative h-64'>
<AgilityImage
src={contact.fields.image.url}
alt={contact.fields.image.label}
className="object-cover object-center rounded-lg"
layout="fill"
/>
</div>
<p className='text-xl mt-2 font-bold'>{contact.fields.name}</p>
<span className='text-gray-600'>{contact.fields.position}</span>
</div>
))}
</div>
<div className='text-center mt-20'>
<button onClick={loadMoreContacts} className="bg-primary-400 text-white p-4 rounded-lg w-40 mx-auto text-center">
{loading ? 'Loading...' : 'Load More'}
</button>
</div>
</div>
)
}
export default ContactsWe can also set up some state for skip, take, loading, and our loadedContacts.
- We set our skip to 4 as we'll want to skip 4 items each time
- We set our take to 4 as we'll want to take 4 items each time
- We set a loading state to show some feedback to the user
- We set loadedContacts to an empty array
Update loadMoreContacts Function
Lets update our loadMoreContacts function with the following code:
const loadMoreContacts = async () => {
setLoading(true)
setSkip(skip + 4)
setTimeout(async () => {
const { data } = await axios.get(`/api/getContacts?skip=${skip}&take=${take}`)
setLoadedContacts(prevState => [...prevState, ...data.contacts])
setLoading(false)
}, 500)
}- We set loading to
trueto show some feedback to the user - We set
skiptoskip+4, this ensure's that we are always skipping the previous 4 contacts when fetching more - We set a timeout of
500msthat fetches contacts from our/api/getContactsAPI endpoint, passing in theskipandtakevalues as query parameters - We set loading back to
false.
Creating Our API Endpoint
We've created a file in the /pages/api directory called getContacts.js with the following code:
import agility from "@agility/content-fetch"
export default async (req, res) => {
const { skip, take } = req.query
const api = agility.getApi({
guid: process.env.AGILITY_GUID,
apiKey: process.env.AGILITY_API_FETCH_KEY
})
const contacts = await api.getContentList({
referenceName: 'contacts',
languageCode: 'en-us',
take: take,
skip: skip
})
res.status(200).json({ contacts: contacts.items })
};
This sets up an instance of our API using the @agility/content-fetch package. We grab skip & take from the request query parameters and use them to page through our API call and return our contacts.
Displaying Loaded Contacts
The final step is to map over the loadedContacts array to display them on the front end. Here's the complete code for our Contacts.js Page Module:
import React, { useState } from 'react'
import { AgilityImage } from "@agility/nextjs"
import axios from "axios"
const Contacts = ({ customData }) => {
const { contacts, totalCount } = customData;
const [skip, setSkip] = useState(4)
const [take, setTake] = useState(4);
const [loading, setLoading] = useState(false)
const [loadedContacts, setLoadedContacts] = useState([])
const loadMoreContacts = async () => {
setLoading(true)
setSkip(skip + 4)
setTimeout(async () => {
const { data } = await axios.get(`/api/getContacts?skip=${skip}&take=${take}`)
setLoadedContacts(prevState => [...prevState, ...data.contacts])
setLoading(false)
}, 500)
}
return (
<div className="max-w-screen-xl mx-auto">
<div className="grid grid-cols-4 gap-8">
{contacts.map((contact, index) => (
<div key={index}>
<div className='relative h-64'>
<AgilityImage
src={contact.fields.image.url}
alt={contact.fields.image.label}
className="object-cover object-center rounded-lg"
layout="fill"
/>
</div>
<p className='text-xl mt-2 font-bold'>{contact.fields.name}</p>
<span className='text-gray-600'>{contact.fields.position}</span>
</div>
))}
{loadedContacts && loadedContacts.map((contact, index) => (
<div key={index}>
<div className='relative h-64'>
<AgilityImage
src={contact.fields.image.url}
alt={contact.fields.image.label}
className="object-cover object-center rounded-lg"
layout="fill"
/>
</div>
<p className='text-xl mt-2 font-bold'>{contact.fields.name}</p>
<span className='text-gray-600'>{contact.fields.position}</span>
</div>
))}
</div>
{totalCount - 4 !== loadedContacts.length && (
<div className='text-center mt-20'>
<button onClick={loadMoreContacts} className="bg-primary-400 text-white p-4 rounded-lg w-40 mx-auto text-center">
{loading ? 'Loading...' : 'Load More'}
</button>
</div>
)}
</div>
)
}
export default Contacts
Contacts.getCustomInitialProps = async ({
agility,
languageCode,
}) => {
const api = agility
try {
const contacts = await api.getContentList({
referenceName: 'contacts',
languageCode,
take: 4,
sort: 'properties.itemOrder',
direction: api.types.SortDirections.ASC
})
return {
contacts: contacts.items,
totalCount: contacts.totalCount
}
} catch (err) {
console.log(err)
}
}