Building Dynamic Pagination in React: Server-Side Fetching with Client Components

Pagination is a core feature for any application dealing with a large amount of data: product listings, blog posts, user lists, etc.

Today, we'll explore how to implement dynamic pagination that fetches data from the server page-by-page, while keeping the UI fast and smooth with React Client Components.


Why Use Dynamic Pagination?

Dynamic pagination means:

  • Fetching only a subset of data (e.g., 10 items at a time) from the server.
  • Avoiding loading all data at once (which slows down apps).
  • Improving load times, SEO, and UX.
  • Reducing server load and bandwidth consumption.

Perfect for ecommerce stores, blogs, dashboards, admin panels, etc.


How Pagination Works (At a High Level)

  1. User visits the page.
  2. Frontend sends a request to the server with a page number.
  3. Server sends back only the relevant items.
  4. Frontend renders them dynamically.
  5. User can navigate between pages, triggering new fetches.

Setting Up the Backend (Fake API Example)

Your backend route might look like this:

GET /api/items?page=2&limit=10

Returns:

{
  "items": [/* 10 items here */],
  "total": 100
}

You need items and total count to calculate how many pages there are.


Frontend: React Pagination Example

Let's build a dynamic pagination system using React client components.

1. Create a Pagination Service

// services/itemService.ts

import axios from 'axios';

export async function fetchItems(page: number, limit: number = 10) {
  const response = await axios.get(`/api/items?page=${page}&limit=${limit}`);
  return response.data;
}

2. Create a PaginatedList Component

// components/PaginatedList.tsx

'use client';

import { useState, useEffect } from 'react';
import { fetchItems } from '@/services/itemService';

export default function PaginatedList() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const limit = 10;

  useEffect(() => {
    async function loadItems() {
      const data = await fetchItems(page, limit);
      setItems(data.items);
      setTotal(data.total);
    }

    loadItems();
  }, [page]);

  const totalPages = Math.ceil(total / limit);

  return (
    <div className="p-4">
      <h2 className="text-xl font-bold mb-4">Itemsh2>

      <ul className="space-y-2">
        {items.map((item: any) => (
          <li key={item.id} className="border p-2 rounded">{item.name}li>
        ))}
      ul>

      <div className="flex gap-2 mt-4">
        <button onClick={() => setPage(p => Math.max(p - 1, 1))} disabled={page === 1} className="px-4 py-2 bg-gray-300 rounded">Previousbutton>

        <span className="px-4 py-2">Page {page} of {totalPages}span>

        <button onClick={() => setPage(p => Math.min(p + 1, totalPages))} disabled={page === totalPages} className="px-4 py-2 bg-gray-300 rounded">Nextbutton>
      div>
    div>
  );
}

Key Features of This Setup

  • Server-Side Fetching:

    • Each page fetches only needed items.
    • Saves bandwidth and improves speed.
  • Client Components:

    • Handles navigation and user interaction instantly.
    • Fetches new data only when needed (on page change).
  • Graceful State Handling:

    • Disable buttons when you reach first or last page.
    • Smooth user experience without page reloads.

Server-Side vs Client-Side Pagination: When to Use

Server-Side vs Client-Side Pagination


Conclusion

Dynamic pagination is an essential building block for scalable, fast, and user-friendly React applications.

By fetching data page-by-page from the server and rendering them smoothly using client-side components, you keep your app lightweight and your users happy.

Start simple — but always keep in mind:

  • Proper state management
  • Loading states and error handling
  • Optimizing API efficiency

Pagination isn't just about navigation — it's about performance. 🚀

Build it right, and your app will scale effortlessly, even when you have millions of records!