Handling Large Datasets in Ant Design: Moving from Client-Side to Server-Side Pagination
Stop lagging your UI with massive arrays. Learn how to implement server-side pagination in Ant Design to handle thousands of rows efficiently while avoiding common 'empty page' bugs.
04 Mar 2026, 00:36 UTC

The Performance Wall of Client-Side Tables
It is tempting to pass a full array of data to the Ant Design Table component and let the built-in pagination handle the slicing. This works perfectly for 50 or 100 rows. However, once your dataset grows to thousands of records, the browser's memory usage spikes, and the UI becomes sluggish. The root cause is that the browser is still processing the entire dataset even if only ten rows are visible.
The solution is server-side pagination: shifting the responsibility of data slicing to your database. Instead of the Table component deciding which rows to show, it becomes a controlled interface that requests specific "chunks" of data from an API based on the current page and page size.
Converting to a Controlled Pagination State
To implement server-side pagination, you must move the pagination state out of the Table's internal logic and into your own component state. By providing the pagination prop as an object, you tell Ant Design that you are managing the current page and page size manually.
The onChange callback is the critical link here. It triggers whenever a user clicks a page number or changes the page size. This function should update your local state and trigger a new API request.
Implementation Example: The Server-Driven Table
This example assumes you are using React and a backend API that accepts page and pageSize as query parameters. Note that Ant Design uses 1-based indexing for pages, while many APIs use 0-based indexing; you may need to subtract 1 from the current page before sending the request.
import React, { useState, useEffect } from 'react';
import { Table } from 'antd';
const ServerTable = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const fetchData = async (page, pageSize) => {
setLoading(true);
try {
// Replace with your actual API endpoint
const response = await fetch(`/api/records?page=${page}&limit=${pageSize}`);
const result = await response.json();
setData(result.items);
setPagination(prev => ({
...prev,
current: page,
pageSize: pageSize,
total: result.totalCount, // Total records available on server
}));
} catch (error) {
console.error('Fetch error:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData(pagination.current, pagination.pageSize);
}, []);
const handleTableChange = (paginationParams) => {
fetchData(paginationParams.current, paginationParams.pageSize);
};
return (
);
};
Execution Details
- Run Location: This code runs in the client-side React application.
- Permissions: Ensure the API endpoint is accessible via CORS or the same origin.
- Expected Check: Verify that the
loadingprop displays the spinner overlay during thefetchDataexecution. - Risk: If
totalis not provided by the API, the pagination controls will not know how many pages to render.
The "Empty Page" Trap
A common engineering oversight occurs when combining pagination with filtering. If a user is on page 10 of a dataset and then applies a filter that reduces the total results to only 2 pages, the table will attempt to request page 10 of the filtered set. Since page 10 no longer exists, the API returns an empty array, and the user sees a blank table despite data existing on page 1.
The Fix: Always reset the current page to 1 whenever a filter, search term, or sort order is changed. This ensures the user is returned to the start of the new result set.
Trade-offs and Limitations
While server-side pagination solves performance issues, it introduces a slight latency increase because every page change requires a network round-trip. To mitigate this, consider implementing a caching layer or using a library like TanStack Query to cache previously visited pages.
Additionally, the Table component's loading prop is a binary state. For extremely slow APIs, you might prefer a skeleton screen, though this requires replacing the Table's internal loading mechanism with a conditional render of a custom skeleton component.
Verification Checklist
- Confirm that clicking the "Next" button triggers the
onChangeevent. - Verify that the
totalcount from the API correctly calculates the number of page buttons displayed. - Test the page size dropdown to ensure the API request updates the
limitparameter. - Apply a filter and verify the current page resets to 1.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.