Server‑Side Pagination with Vuetify v‑data‑table for Large Datasets
Learn how to offload pagination to the server with Vuetify's v-data-table, reducing memory usage and keeping the UI responsive for large datasets.
27 Jan 2026, 00:23 UTC

Why client‑side pagination hurts large tables
When a v-data-table receives thousands of rows, the browser must keep every record in the DOM and in JavaScript memory. Scrolling, sorting, or filtering becomes sluggish because the framework re‑renders the whole list on each interaction. The result is a noticeable lag and higher memory consumption, especially on low‑end devices.
Switching to server‑side pagination
Vuetify’s v-data-table can work with a subset of rows if you provide the current page data via the :items prop and tell the component how many rows exist in total with :total-items. The table then emits @update:options whenever the user changes page, sort order, or filters. By handling that event you can request only the needed slice from a backend API.
Key props and events
:items– array of rows for the current page.:total-items– total number of rows available on the server.:options– object containingpage,itemsPerPage,sortBy,sortDesc, and any filters.@update:options– fired after the user changes pagination, sorting, or filtering.
Worked example
The following single‑file component demonstrates the pattern. Replace the placeholder URL with your actual endpoint.
<template>
<v-data-table
:headers="headers"
:items="rows"
:total-items="total"
:options="options"
@update:options="fetchPage"
class="elevation-1"
>
<template #progress>
<v-progress-circular indeterminate color="primary" />
</template>
</v-data-table>
</template>
<script setup>
import { ref } from 'vue';
const headers = [
{ text: 'ID', value: 'id' },
{ text: 'Name', value: 'name' },
{ text: 'Email', value: 'email' },
];
const rows = ref([]); // data for the current page
const total = ref(0); // total records on the server
const options = ref({
page: 1,
itemsPerPage: 10,
sortBy: 'id',
sortDesc: false,
});
async function fetchPage(payload) {
// payload is the new options object
options.value = payload;
try {
const resp = await fetch(
`/api/items?page=${payload.page}` +
`&perPage=${payload.itemsPerPage}` +
`&sortBy=${payload.sortBy}` +
`&sortDesc=${payload.sortDesc}`
);
if (!resp.ok) throw new Error('Network error');
const data = await resp.json(); // expect { items: [...], total: 12345 }
rows.value = data.items;
total.value = data.total;
} catch (err) {
console.error(err);
rows.value = [];
total.value = 0;
}
}
// initial load
fetchPage(options.value);
</script>
The component requests a fresh slice each time the user changes page, sorts, or applies a filter. While waiting for the response the #progress slot shows a spinner, giving immediate feedback.
Trade‑offs and limitations
Moving pagination to the server introduces a network round‑trip for every UI interaction, which can add noticeable latency if the backend is slow or the client is on a high‑lag connection. The API must support filtering, sorting, and accurate total‑count calculations; building those endpoints adds development effort. If the total count changes frequently (e.g., rows are inserted or deleted by other users), the table’s page numbers may shift unexpectedly unless you refresh or use optimistic updates.
Verification steps
- Open Chrome DevTools → Network tab, enable “Preserve log”.
- Change a page in the table and confirm exactly one XHR/fetch request appears.
- Check that the response JSON contains an
itemsarray and atotalfield. - Observe that the table shows the correct number of rows (
itemsPerPage) and that the page selector updates whentotalchanges. - Run the page with a large dataset (e.g., 10 000 rows) and monitor memory usage in the Performance tab; it should stay low and the UI should remain responsive.
Actionable closing
Start by mocking the API with a simple Node/Express route that returns a static slice and a fixed total. Implement the component above, verify the network behavior, then replace the mock with your real data source. After deployment, keep an eye on request latency and consider adding debouncing or client‑side caching if rapid page changes become a problem.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.