Diagnosing Vuetify v-data-table Server‑Side Pagination Stale Data Issues
Learn why Vuetify’s v-data-table shows stale data with server‑side pagination and how to fix it by checking :server-items-length and server-items props.
29 Jun 2026, 11:48 UTC

Problem you see
When using v-data-table with server‑side pagination in a Vue 3 app, the table either shows the same rows on every page or does not update when you click the pagination controls. The UI may appear frozen, or the row count stays zero even though the API returns data.
Quick takeaway
Ensure that :server-items-length receives the exact total record count from the backend and that :server-items contains only the slice for the current page (length ≤ itemsPerPage). If either value is missing or malformed, Vuetify ignores pagination events and keeps the stale view.
Cause / diagnostic table
| Symptom | Likely cause |
|---|---|
| Table shows first page data on all pages | :server-items-length is zero, undefined, or not an integer |
| Pagination controls are disabled or do not trigger a request | :server-items length does not match the expected page size (or exceeds it) |
Network request never changes page or itemsPerPage | Manual pagination logic mutates the pagination object directly, breaking Vuetify’s internal sync |
Ordered checks
- Verify props type and value
- Open the Vue DevTools → select the
v-data-tablecomponent. - Check that
serverItemsLengthis a non‑negative integer (e.g.,1234). - Check that
serverItemsis an array whose length equalsitemsPerPagefor all but possibly the last page.
Where to run: Browser devtools, no special permissions needed.
- Open the Vue DevTools → select the
- Inspect the API response
- In the Network tab, find the request triggered by a page change.
- Confirm the JSON includes a field (e.g.,
totalCount) that you bind to:server-items-length. - Confirm the
dataarray length matches the requesteditemsPerPage(or fewer on the last page).
Where to run: Browser → DevTools → Network.
- Check pagination event emission
- Add a temporary listener:
@update:pagination="logPagination"wherelogPagination = (val) => console.log(val). - Click next/previous page and verify the logged object contains updated
pageanditemsPerPagevalues.
Where to run: In your component template or script.
- Add a temporary listener:
- Validate manual pagination handling (if used)
- If you call
update:paginationyourself, ensure you spread the payload:this.$emit('update:pagination', { ...payload }). - Avoid mutating the received object directly.
- If you call
Fixes tied to findings
- Missing or wrong total count
<v-data-table :server-items-length="totalCount" :server-items="pageRows" @update:pagination="onPagination" />In your script:
export default { data() { return { totalCount: 0, pageRows: [], pagination: { page: 1, itemsPerPage: 10 } }; }, methods: { async onPagination(pagination) { this.pagination = pagination; // keep a copy const resp = await fetch(`/api/items?page=${pagination.page}&perPage=${pagination.itemsPerPage}`); const json = await resp.json(); this.totalCount = json.totalCount; // ← must be integer this.pageRows = json.data; // ← slice for this page } } }; - Incorrect server‑items length
Ensure the backend returns exactly
itemsPerPagerows (or fewer on the last page). If you accidentally return the full dataset, trim it before assigning toserverItems:this.pageRows = json.data.slice(0, pagination.itemsPerPage); - Direct mutation of pagination object
Replace patterns like:
// ❌ Bad pagination.page++;With:
// ✅ Good this.$emit('update:pagination', { ...pagination, page: pagination.page + 1 });
Escalation criteria
If after applying the fixes the table still does not paginate:
- Compare the request query parameters (
page,itemsPerPage,sortBy,descending) sent by the UI with those actually received by the server. - Look for missing or mismatched parameters; the server may be ignoring the request and returning the first page.
- Check for middleware that alters or drops query strings (e.g., a proxy that rewrites
_pageinstead ofpage). - Verify that no global error handler is swallowing the fetch promise, preventing the component from updating
serverItemsLength.
Limitations and practical verification
This guide assumes you are using Vuetify 3.x with Vue 3 and that the backend follows a REST‑style pagination contract (page‑based, not cursor‑based). If your API uses cursor pagination, the :server-items-length approach does not apply and you would need to switch to client‑side pagination or a custom solution.
To confirm the fix works:
- Open the table, go to the last page, and verify the row count matches the remaining items (
totalCount % itemsPerPage). - Open DevTools → Network, change pages, and ensure each request includes the correct
pageanditemsPerPagevalues. - Check that the UI updates without needing a manual page reload.
Example configuration
Below is a minimal, working snippet you can paste into a .vue file to test the pattern:
<template>
</template>
<script setup>
import { ref } from 'vue'
const headers = [
{ title: 'Name', align: 'start', key: 'name' },
{ title: 'Age', align: 'end', key: 'age' }
]
const total = ref(0)
const rows = ref([])
const pagination = ref({ page: 1, itemsPerPage: 10, sortBy: [], descending: [] })
async function fetchData(pagination) {
const resp = await fetch(
`/api/users?page=${pagination.page}&perPage=${pagination.itemsPerPage}`
)
const json = await resp.json()
total.value = json.totalCount // integer
rows.value = json.data // slice for this page
}
// initial load
fetchData(pagination.value)
</script>
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.