Building Dynamic Pagination in React: Server-Side Fetching with Client Components
Apr 28 • 3 min read
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)
- User visits the page.
- Frontend sends a request to the server with a page number.
- Server sends back only the relevant items.
- Frontend renders them dynamically.
- 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">Items</h2> <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">Previous</button> <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">Next</button> </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

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!