← Back to All Posts

webdev, javascript, nextjs, react

Data Pagination in Next.js with Custom Hooks

Written by: @stephengade ↔ on Dec 9 '23

Data pagination is a concept you are familiar with even if you're not a web developer. You have seen and read different books, and when you finish reading page 1 of the book, you open to page 2 and so on.

Imagine there is only one page for a whole textbook, do you think it will be easy for you to read and navigate through it? Definitely not.

That's the same problem your user may face when you ignore pagination in your web application. Not only that but also: The website will be slow as a lot of data has to be loaded and displayed.

The bigger problem is the heavy load on your server as it has to return a large volume of data at once

Imagine you are working at Shopify, a company with over 1.7m merchants, and you are tasked to create a page that returns the basic information of each of the 1.7m merchants. But if you ignore pagination, you will have to face your manager and explain what motivates you.

Pagination is not only needed when you're handling millions of data but also when you want to organize and present a large volume of data very nicely to the user.

But where is the best place to handle data pagination?

Without mincing a word, the best place to handle pagination is the backend. Because the backend engineers have access to the database and they design the APIs, it is easy for them to paginate how the data is sent to the frontend.

Not only because it is easy for them, but also optimizes the overall performance of both frontend and backend.

That doesn't mean as a frontend developer, you have no business with data pagination, you do because you have to handle the frontend logic for pagination as well.

That's what we will do in this tutorial.

Nextjs hook for Data pagination

But we don't have an API to work with, so I will be using mockup data. But it's still the same experience you'll have while working with a backend app.

Simulating Backend Data Pagination

A typical backend API that returns paginated data will only allow you to fetch a specific number of data at once (for instance 20), and if the user needs to see more, they have to request another 20, and so on.

So the data of 115 users will be paginated to 6 pages. That is, 20 users for each of the 5 pages, while the 6th page will have 15 users.

The api can be in this format: {{BaseURL}}/data?page={currentPage}&limit={dataLimit}

  • The BaseURL is the host domain name of the API
  • /data is the exact location where the users are stored
  • ?page={currentPage} helps the frontend and backend to know which page is the user currently
  • &limit={dataLimit} means how much data should be returned at once.

Let's say the backend has 115 users and you want to display 20 users at once, that's the limit.

On initial fetch at the frontend, the currentPage will be 1 showing just 20 users only.

If the backend has paginated the data already (which should be), please use the flow when handling your frontend pagination, don't do it raw on the frontend because the essence of paginated won't be achieved. So stick to backend API documentation.

If you click on NEXT, the currentPage will be updated to 2 telling the backend to send the next 20 users and so on. If you click PREV, it'll decrement the currentPage, i.e. page 2 becomes 1.

** Let's get to work now **

Step 1: Bootstrap a new Next.js app

If you followed my previous article, you don't need to create a new Next.js app as I will be continuing from where I stopped there.

You can check the article: Build a Functional Search Bar in Next.js

Or you can create a new Next.js app by running yarn create next-app and following the prompts.

Then proceed to create a mockup data of 100 users or you can fetch 100 fake posts with this API: https://jsonplaceholder.typicode.com/posts

Display the response with a nice PostCard. You should be seeing 100 posts. If that works, let's go to the next step.

Step 2: Creating the Pagination Hook

Hooks in React help us use functions or logic over and over again without rewriting that function again.

Whether you're using a backend API or mockup data, we will create custom hooks for both cases.

In your src folder, create a new folder called hooks. You can put all your Hook files in this folder for consistency.

Inside the hooks folder, create a new file called usePaginate.tsx or with .jsx if you're not using Typescript.

Put this code there:

{% gist https://gist.github.com/stephengade/7fbaa04ffb7fda3a5eb82140da837480 %}

I have explained what each line of code does with comments. In the code, there are two different hooks called there to handle the two scenarios.

Kindly note that I am using axios to fetch data, you can use the native fetch too.

Step 3: Update your data component with the Hook

import the pagination hook concerning if you're working with mockup data or backend data.

If you're following my code, I created a button component to use for the Prev and Next buttons, here is it:

// components/Button.tsx

interface iButton {
    CTA: string
    onClick: () => void
    disabled: boolean

export const Button = ({CTA, onClick, disabled}: iButton) => {
    return (
        <button disabled={disabled} onClick={onClick} className={`bg-blue-700 rounded-[32px] shadow-md hover:bg-blue-500 text-white font-bold py-2 px-5 ${disabled && "pointer-events-none bg-slate-300"}  `}>

Now, we need to plug these buttons into the logic.

For mockup data flow:

I assume you have displayed your mockup data already using the useState and useEffect hooks. For instance:

// must have imported the user data

  const [userData, setUserData] = useState([])

  useEffect(() => {
  }, [])

return (
  {userData.map(({title… }) => {
    return <Card title={title}. />


If that's what you've done, we just need to update the logic with our paginatedData hook. Remember that we must provide the mockData and the numbers of data we want to display at once.

The updated code will be:

const [userData, setUserData] = useState([])

  useEffect(() => {
  }, [])

// the hook

const {nextPage, prevPage, paginatedData, currentPage} = useMockPaginate(userData, 12)

// ….

// you map and display the paginatedData destructure from useMockPaginate Hook to the.

return (
  {paginatedData.map(({title… }) => {
    return <Card title={title}. />

{* the buttons *}

 <div className="flex flex-row justify-end items-center gap-12 mt-12">
        <Button CTA="Previous" onClick={prevPage} disabled={currentPage === 1 && true} />
        <Button CTA="Next" onClick={nextPage} disabled={false} />


That's all, if we test our application, it's working as expected. If you're following my previous code, you can still see the full and updated code here.

for the backend API data flow

If you're using an API, don't use the useMockPaginate hook as it won't work, use the useDataPaginate hook instead.

Remember that we must provide our API endpoint to the hook.

Before you use the Hook, ensure you understand the API structure, i.e., how the API queries are structured to take the currentPage and data limit.

For instance, for the posts API I recommended above, the full API URL with pagination is: https://jsonplaceholder.typicode.com/posts?_page=${currentPage}&_limit={limitData}

NB: Note that I have programming to insert the paginating queries in the useDataPaginate hook, what you need to provide when using the hook is the actual API endpoint (https://jsonplaceholder.typicode.com/posts) and how many users you want to return.

What we only need to do is to pass and update the values of currentPage and limitData as the heavy work has been done at the backend.

Now let's use our hook to update those parameters.

Assuming you already have a page where you want to display and paginate the data, the code will look something like:

// import the useDataPaginate hook

const App = () => {

//Define the limit of data to fetch

const dataLimit = 10; // number of data you want to show on a page

// destructure functions from the hook 

const API_URL = `https://jsonplaceholder.typicode.com/posts`

const {nextPage, prevPage, data, currentPage} = UseDataPaginate(API_URL, dataLimit)

// map the destructure `data` 
  return (
  {data.map(({title… }) => {
    return <Card title={title}. />

{* the buttons *}

<div className="flex flex-row items-center justify-between">

 <button onClick={prevPage}>Previous</button>
 <button onClick={nextPage}>Next</button>




export default App

And you're good to go. If you're following my code, here is the demo:

Data pagination in React Nextjs

The full code can be found in this repository.


In this article, we have learned how to create a custom Hook. A custom hook in the simplest terms is simply a reusable function in React that lets you use logic over over over again and we have leveraged that to handle Data pagination in our React (Nextjs) application.

Whether you're using mockup data or fetching data from an API, those two hooks cover you.

But remember to always use the backend pagination flow as it is the most efficient.

Now that we are done with searching and pagination, what happens when you click on a profile or data card? Nothing.

But it should navigate us to a dedicated page about that particular user or data to learn more about.

In my next article, I will be talking about dynamic routing. Stay tuned.

Open to Work

I am actively open for a remote frontend developer and Technical writer role. Kindly contact me if you are hiring


Ready to work together?

I am actively seeking new opportunities and am available for remote, freelance or contract work.

If you're interested in discussing potential projects or learning more about my skills and experience, please feel free to contact me. I look forward to connecting with you and exploring how I can contribute to your organization's success remotely.