Configuring Paginated Product Lists in Vue Storefront with GraphQL
Learn how to paginate product lists in Vue Storefront using the useProductList composable, set page and pageSize variables, avoid common pitfalls, and verify the GraphQL requests.
16 Jun 2026, 14:46 UTC

Quick answer
To fetch paginated product collections in Vue Storefront, set the page and pageSize variables when calling the useProductList composable (or its GraphQL hook equivalent). The composable updates the URL query string, returns data, loading and error state, and forwards the variables to the backend GraphQL resolver, which returns a total count alongside the product array.
Worked example
The following snippet shows a typical product‑list page component that lets the user choose a page size via a dropdown and navigates pages with “Next”/“Previous” buttons.
<template>
<div>
<label>Page size:
<select v-model="pageSize" @change="resetPage">
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
</label>
<button :disabled="loading || page === 1" @click="goPrev">Previous</button>
<span>Page {{ page }}</span>
<button :disabled="loading || page * pageSize >= total" @click="goNext">Next</button>
<ul v-if="!loading&&data">
<li v-for="product in data" :key="product.id">
{{ product.name }}
</li>
</ul>
<p v-else-if="loading">Loading…</p>
<p v-else-if="error">Error: {{ error.message }}</p>
</div>
</template>
<script setup>
import { useProductList } from '@vuestorefront/core'
import { ref, watch } from 'vue'
const page = ref(1)
const pageSize = ref(20) // initial page size
const { data, loading, error, total } = useProductList({
filter: { categoryId: '123' }, // example filter
page,
pageSize
})
function goPrev() { if (page.value > 1) page.value-- }
function goNext() { page.value++ }
function resetPage() { page.value = 1 }
// Keep URL in sync for deep linking
watch([page, pageSize], () => {
// The composable already updates the router; this watch is just for illustration
}, { deep: true })
</script>
How it works
- The composable receives an options object containing
filter,pageandpageSize. - It builds a GraphQL query where these values become variables
$currentPageand$pageSize(the exact names depend on the backend schema but are forwarded unchanged). - The Apollo/urql client sends the request; the resolver queries the catalog, applies pagination limits, and returns:
data: array of product objects for the requested slice.total: total number of products matching the filter.- While the request is in flight,
loadingis true; on successdataandtotalare populated; on failureerroris set. - The composable also updates the Vue Router query string (
?page=2&pageSize=20) enabling deep linking and browser back/forward navigation.
Limits and common mistakes
- Backend page‑size caps: Most catalog services enforce a maximum
pageSize(often 100‑200). If you request a larger value the resolver either truncates the result or returns a validation error. Always check the server’s documentation and clamp the UI dropdown to the allowed maximum. - Stale‑while‑revalidate cache: The integrated Apollo/urql client caches each unique
({page, pageSize, filter})request. Repeated navigation to the same page hits the cache unless you callinvalidateafter cart modifications. Forgetting to invalidate can show outdated stock levels. - Not resetting
pageon filter change: If you switch categories or change thefilterobject without resettingpageto 1, the composable keeps the previous page number, causing the UI to display products from the wrong offset or an empty list. - Ignoring the
totalfield: Relying solely on the length of the returneddataarray to decide whether a “Next” button should be shown can lead to infinite loading loops when the last page contains fewer items thanpageSize. Comparepage * pageSizewithtotal(as shown in the button disabled condition). - URL drift: Manually pushing a new route without updating the composable’s
pageorpageSizevariables desynchronizes UI and state, causing stale data to be displayed until the next automatic refetch.
Verification steps
- Open browser dev tools → Network tab, filter for GraphQL requests to the catalog endpoint.
- Confirm that the request payload includes variables
"currentPage":<value>and"pageSize":<value>matching the UI controls. - Inspect the response JSON; ensure it contains a
totalfield whose value equals the expected number of products for the current filter. - Change the page via the “Next” button and verify that the URL query string updates (
?page=3&pageSize=20) and that a new GraphQL request is sent with the updatedcurrentPage. - After adding a product to the cart, call the cart invalidation method (provided by the storefront’s cart module) and confirm that the next product‑list request bypasses the cache (look for a cache‑miss indicator in the Network tab).
Practical way to check the result
After implementing the component, navigate to a category with known product count (e.g., 23 items). Set pageSize to 10. The UI should show:
- Page 1: 10 items, “Next” enabled.
- Page 2: 10 items, “Next” enabled.
- Page 3: 3 items, “Next” disabled because
3 * 10 ≥ 23evaluates to true.
If the “Next” button remains enabled on the last page, revisit the condition that compares page * pageSize with total.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.