How to Implement Pagination in Next.js

Introduction

Pagination helps manage large datasets by dividing them into manageable chunks. You can implement pagination in Next.js either server-side or client-side. Here's a streamlined guide to get you started.

1. Server-Side Pagination
 

1.1 Create an API Route

Set up an API route to handle pagination.

// pages/api/posts.js
export default async function handler(req, res) {
  const { page = 1, limit = 10 } = req.query;

  // Mock data
  const posts = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, title: `Post ${i + 1}` }));

  const startIndex = (page - 1) * limit;
  const paginatedPosts = posts.slice(startIndex, startIndex + limit);

  res.status(200).json({
    data: paginatedPosts,
    total: posts.length,
    page: parseInt(page),
    totalPages: Math.ceil(posts.length / limit),
  });
}

1.2 Fetch Data on a Page

Fetch paginated data using getServerSideProps.

// pages/posts.js
import Link from 'next/link';

const PostsPage = ({ data, page, totalPages }) => (
  <div>
    <h1>Posts</h1>
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
    <div>
      {page > 1 && (
        <Link href={`/posts?page=${page - 1}`}>
          <a>Previous</a>
        </Link>
      )}
      {page < totalPages && (
        <Link href={`/posts?page=${page + 1}`}>
          <a>Next</a>
        </Link>
      )}
    </div>
  </div>
);

export async function getServerSideProps(context) {
  const { query } = context;
  const page = parseInt(query.page) || 1;
  const res = await fetch(`http://localhost:3000/api/posts?page=${page}`);
  const { data, totalPages } = await res.json();

  return { props: { data, page, totalPages } };
}

export default PostsPage;

2. Client-Side Pagination
 

2.1 Setup State Management

Use state to manage pagination on the client side.

// components/Posts.js
import { useState, useEffect } from 'react';

const Posts = ({ initialData, initialPage, totalPages }) => {
  const [posts, setPosts] = useState(initialData);
  const [page, setPage] = useState(initialPage);

  useEffect(() => {
    const fetchPosts = async () => {
      const res = await fetch(`/api/posts?page=${page}`);
      const { data } = await res.json();
      setPosts(data);
    };
    fetchPosts();
  }, [page]);

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
      <div>
        {page > 1 && (
          <button onClick={() => setPage(page - 1)}>Previous</button>
        )}
        {page < totalPages && (
          <button onClick={() => setPage(page + 1)}>Next</button>
        )}
      </div>
    </div>
  );
};

export default Posts;

2.2 Use the Component

Integrate the client-side pagination component in a page.

// pages/posts.js
import Posts from '../components/Posts';

const PostsPage = ({ initialData, initialPage, totalPages }) => (
  <Posts
    initialData={initialData}
    initialPage={initialPage}
    totalPages={totalPages}
  />
);

export async function getServerSideProps(context) {
  const { query } = context;
  const page = parseInt(query.page) || 1;
  const res = await fetch(`http://localhost:3000/api/posts?page=${page}`);
  const { data, totalPages } = await res.json();

  return {
    props: {
      initialData: data,
      initialPage: page,
      totalPages,
    },
  };
}

export default PostsPage;

3. Best Practices

  • Optimize Data Fetching: Load only necessary data to avoid performance issues.
  • User Experience: Provide intuitive navigation and loading indicators.
  • Error Handling: Implement error handling for data fetch failures and edge cases.

Summary

Pagination in Next.js can be handled either server-side or client-side. By setting up an API route and using appropriate data fetching techniques, you can effectively manage large datasets and enhance user experience. Follow best practices to ensure your pagination implementation is efficient and user-friendly.