Diagnose Vuetify v-data-table Server‑Side Pagination Issues (v3.x)
Diagnose and fix Vuetify v-data-table server‑side pagination problems in Vue 3. Follow a diagnostic flow that covers API response, reactive options, pagination sync, and total‑count accuracy.
20 Dec 2025, 08:57 UTC

Problem Overview
When using Vuetify 3’s v-data-table with server‑side pagination, developers frequently hit a handful of symptoms:
- Table shows no rows even though the API returns data.
- Pagination controls stay static or jump unpredictably.
- Changing page, sort or filter does not trigger a new API call.
- Displayed total row count is wrong.
- Console shows warnings about missing
keyoritemprops. - Data appears stale or freezes when navigating pages.
These symptoms usually point to a mismatch between the component’s reactive state and the API contract. The following diagnostic guide helps you locate the root cause, apply the appropriate fix, and verify that the table behaves as expected.
Symptom & Cause Table
| Symptom | Likely Cause | Immediate Check |
|---|---|---|
| No rows displayed | items prop is undefined/empty or not reactive | Console log items just before rendering. |
| Pagination jumps or stays static | Internal page state not synced with page prop | Inspect options.page after a click. |
| No API call on interaction | Watcher on options missing or not reactive | Verify watch(options, …) exists and is deep. |
| Wrong total count | server-items-length mismatched with API total | Compare API’s total with the prop value. |
Console warnings about key or item | Custom row slot missing key or wrong prop name | Check slot template for key and item usage. |
| Stale data | Vue reactivity not detecting array mutation | Replace array instead of mutating in place. |
Diagnostic Flow
- Confirm the API response
- Open DevTools → Network and locate the request triggered by the table.
GET /api/items?page=1&itemsPerPage=10&sortBy=name&sortDesc=false - Verify the JSON payload contains
items: [...]andtotal: 123.
- Open DevTools → Network and locate the request triggered by the table.
- Validate the
itemsprop- In
setup()(ordata()for Options API), log the variable that feedsitems:console.log('items', items.value); // Vue 3 ref - If it’s
undefinedor[]when the API returns data, the fetch logic is broken.
- In
- Ensure
optionsis reactive- Define
optionsas areforreactiveobject:const options = ref({ page: 1, itemsPerPage: 10, sortBy: [], sortDesc: [], }); - Set up a watcher that triggers the API call:
watch(options, async (newOpts) => { await fetchData(newOpts); }, { deep: true }); - Without
deep, nested changes (e.g.,sortBy) won’t trigger the watcher.
- Define
- Sync internal page state
- Pass the same ref to the
pageprop and bind theupdate:optionsevent:<v-data-table :items="items" :options="options" :server-items-length="total" :page="options.page" @update:options="options = $event" > </v-data-table> - Failing to do this causes the component’s internal page counter and your
options.pageto diverge.
- Pass the same ref to the
- Keep
server-items-lengthaccurate- After each API response, set:
total.value = response.data.total; - If
totalstays at 0, the pagination UI will always show a single page.
- After each API response, set:
- Check row rendering
- When using a custom row slot, always provide a unique
key:<template #item="{ item }" :key="item.id"> {{ item.name }} </template> - Missing
keytriggers Vue warnings and may prevent re‑rendering.
- When using a custom row slot, always provide a unique
- Reactivity pitfall: mutate vs. replace
- Don’t push into the existing array:
items.value.push(newItem); // Vue may ignore this mutation - Replace the array to guarantee a new reference:
items.value = [...items.value, newItem];
- Don’t push into the existing array:
Concrete Example – Minimal Server‑Side Pagination Component
Below is a minimal setup()-based component that follows the diagnostic fixes. Replace the fetchData function with your API call logic.
import { ref, watch } from 'vue';
export default {
setup() {
const items = ref([]);
const total = ref(0);
const options = ref({
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: [],
});
async function fetchData(opts) {
const res = await fetch(`/api/items?page=${opts.page}&itemsPerPage=${opts.itemsPerPage}`);
const data = await res.json();
items.value = data.items;
total.value = data.total;
}
// Initial load
fetchData(options.value);
// React to page/sort/filter changes
watch(options, async (newOpts) => {
await fetchData(newOpts);
}, { deep: true });
return { items, total, options };
},
};
Use it in the template like this:
<v-data-table
:items="items"
:server-items-length="total"
:options="options"
:page="options.page"
@update:options="options = $event"
:loading="items.length === 0"
>
<template #item="{ item }" :key="item.id">
{{ item.name }}
</template>
</v-data-table>
Escalation Criteria
- If the table still shows stale data after following the steps, inspect the API for caching or stale‑data headers.
- When pagination jumps remain, verify that you are using a recent Vuetify 3.x release and consult the release notes for any known pagination bugs.
- If console warnings about missing
itemprops persist, double‑check that any custom slots use the correct prop names.
Practical Verification Checklist
- Open DevTools → Network → confirm the API request contains the expected query params.
- Inspect
optionsin Vue DevTools and watch its changes when clicking page numbers. - Check
itemsarray length after each fetch; it should match the number of rows rendered. - Verify
server-items-lengthmatches the API’stotal. - Look for
Vue warnmessages about missingkeyoritemprops.
Limitations & Caveats
- The guide assumes Vuetify 3.0+ and Vue 3.x. Vuetify 2 uses different props (e.g.,
total-items). - Server‑side pagination logic must be idempotent; if the API changes the page size mid‑session, the UI may mis‑represent total pages.
- When using SSR or hydration, ensure the initial
optionsstate matches what the server renders to avoid flicker.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.