Implementing Server-Side Pagination and Sorting in Ant Design Tables
Learn how to implement server-side pagination and sorting in Ant Design Tables to handle large datasets without crashing the browser or slowing down the UI.
03 Apr 2026, 13:23 UTC

The Problem: Client-Side Memory Limits
By default, the Ant Design Table component handles pagination and sorting on the client side. This requires the entire dataset to be loaded into the browser's memory. When dealing with datasets exceeding a few hundred rows, this approach leads to significant browser lag, slow initial load times, and potential crashes.
The solution is to shift the data processing to the backend. Instead of passing a full array to the dataSource prop, the Table becomes a controlled component that requests only the specific slice of data needed for the current view.
Prerequisites
- A React project with
antdinstalled (v4.x or v5.x). - A backend API that supports query parameters for
page,pageSize, andsortOrder. - A state management strategy (such as
useStateoruseReducer) to track the current table state.
Implementation Procedure
1. Define the Column Configuration
To enable server-side sorting, you must set sorter: true in the column definition. This tells Ant Design to render the sort icons and trigger the onChange event, but prevents the component from attempting to sort the data locally.
const columns = [
{
title: 'User Name',
dataIndex: 'name',
sorter: true, // Enables UI sorting triggers
},
{
title: 'Email',
dataIndex: 'email',
},
];
2. Manage Table State
You must track the current page, page size, and sorting parameters in your component state. If you do not manage these manually, the Table will not visually update when the user interacts with the pagination or headers.
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
});
const [sortConfig, setSortConfig] = useState({});
3. Handle the onChange Callback
The onChange prop is the central hub for all table interactions. It provides the pagination object, filters, and sorters. Use this to trigger your API call.
const handleTableChange = async (pagination, filters, sorter) => {
// 1. Update local state to reflect UI changes immediately
setPagination(pagination);
// 2. Extract sorting info (sorter is an object or array of objects)
const sortField = sorter.field || '';
const sortOrder = sorter.order || 'ascend';
setSortConfig({ sortField, sortOrder });
// 3. Fetch new data from the server
await fetchData(pagination.current, pagination.pageSize, sortField, sortOrder);
};
const fetchData = async (page, size, field, order) => {
setLoading(true);
try {
// Example API call: /api/users?page=1&size=10&sort=name&order=ascend
const response = await fetch(`/api/users?page=${page}&size=${size}&sort=${field}&order=${order}`);
const result = await response.json();
setData(result.list);
// Update total elements for the pagination UI
setPagination(prev => ({ ...prev, total: result.total }));
} catch (error) {
console.error('Fetch failed:', error);
} finally {
setLoading(false);
}
};
4. Configure the Table Component
Connect the state and handlers to the Table component. Ensure pagination is passed as an object to maintain control.
<Table
columns={columns}
dataSource={data}
loading={loading}
onChange={handleTableChange}
pagination={{
...pagination,
showSizeChanger: true
}}
/>
Verification and Diagnostics
To ensure the implementation is working correctly, perform the following checks:
- Network Inspection: Open the Browser DevTools Network tab. Clicking a page number or a column header should trigger a new XHR/Fetch request with updated query parameters.
- Loading State: Verify that the
loadingprop displays the spinner overlay while the API request is pending. - UI Sync: Confirm that the active page number in the pagination component matches the data being displayed.
Comparison: Client-Side vs Server-Side
| Feature | Client-Side (Default) | Server-Side (Implemented) |
|---|---|---|
| Initial Load | Slow (fetches all data) | Fast (fetches one page) |
| Memory Usage | High (scales with dataset) | Low (constant per page) |
| Sorting Speed | Instant (after load) | Dependent on API latency |
| Implementation | Zero config | Requires API support + State management |
Limitations and Risks
- API Dependency: Setting
sorter: trueonly changes the UI. If the backend does not implement the sorting logic, the data will remain unsorted despite the arrow icon appearing in the header. - DOM Performance: While server-side pagination solves data loading, setting a
pageSizetoo high (e.g., > 200 rows) can still cause rendering lag due to the number of DOM elements created by Ant Design. - State Desync: If you update the data source without updating the
pagination.currentstate, the table may display the correct data but show the wrong page number.
Rollback Strategy
If the server-side implementation causes instability or the API is unavailable, revert to client-side processing by:
- Removing the
onChangehandler from the Table. - Removing the
paginationstate and passing a simple booleanpagination={true}. - Changing
sorter: trueto a custom sorting function:sorter: (a, b) => a.name.localeCompare(b.name). - Fetching the entire dataset once during the
useEffectmount phase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.