usePagination
The usePagination composable provides URL-synced pagination state management. It automatically handles page and page size changes through URL query parameters, making pagination states shareable and bookmarkable. Works seamlessly with Nuxt UI's UPagination and USelect components.
Basic Usage
<script setup lang="ts">
const { page, pageSize } = usePagination()
// Fetch data with current pagination
const { data } = await useFetch('/api/users', {
query: {
page,
size: pageSize,
},
})
</script>
<template>
<div>
<!-- Your data display -->
<UserList :users="data.items" />
<!-- Pagination controls -->
<div class="flex items-center justify-between gap-4">
<USelect
v-model="pageSize"
:options="[10, 25, 50, 100]"
/>
<UPagination
v-model="page"
:total="data.total"
:page-size="pageSize"
/>
</div>
</div>
</template>
Options
The usePagination composable accepts an optional configuration object:
| Property | Type | Default | Description |
|---|---|---|---|
defaultPage | number | 1 | The default page number when no query parameter is present |
defaultSize | number | 10 | The default page size when no query parameter is present |
route | ReturnType<typeof useRoute> | useRoute() | The route object to read query parameters from |
Return Value
Returns an object with two reactive computed properties:
page- Current page number (getter/setter that updates URL)pageSize- Current page size (getter/setter that updates URL)
Both properties automatically:
- Read from URL query parameters (
?page=2&size=25) - Update URL when changed (via
navigateTo) - Remove default values from URL for cleaner links
Examples
With Custom Defaults
const { page, pageSize } = usePagination({
defaultPage: 1,
defaultSize: 25, // Start with 25 items per page
})
Server-Side Pagination
<script setup lang="ts">
const { page, pageSize } = usePagination()
const { data, pending, refresh } = await useFetch('/api/products', {
query: {
page,
size: pageSize,
},
})
</script>
<template>
<div>
<UProgress v-if="pending" />
<ProductGrid :products="data.items" />
<div class="flex justify-between mt-6">
<USelect
v-model="pageSize"
:options="[10, 25, 50]"
label="Items per page"
/>
<UPagination
v-model="page"
:total="data.total"
:page-size="pageSize"
/>
</div>
</div>
</template>
With Search and Filters
<script setup lang="ts">
const route = useRoute()
const { page, pageSize } = usePagination({ route })
// Other query parameters
const search = computed(() => route.query.search as string || '')
const { data } = await useFetch('/api/posts', {
query: {
page,
size: pageSize,
search,
},
})
</script>
<template>
<div>
<UInput v-model="search" placeholder="Search posts..." />
<PostList :posts="data.items" />
<UPagination
v-model="page"
:total="data.total"
:page-size="pageSize"
/>
</div>
</template>
Multiple Paginated Lists
When you have multiple paginated lists on the same page, pass different route instances or use different query parameter names:
<script setup lang="ts">
// For the main list
const { page: userPage, pageSize: userPageSize } = usePagination({
defaultSize: 10,
})
// For a secondary list, you'd need custom query params
// (the composable currently uses 'page' and 'size' by default)
</script>
Behavior Details
URL Query Synchronization
The composable keeps pagination state in sync with URL query parameters:
- On mount: Reads
?page=X&size=Yfrom URL - On change: Updates URL via
navigateTo - Default values: Removed from URL (e.g.,
?page=1becomes/)
// URL: /products
// page = 1 (default), pageSize = 10 (default)
page.value = 2
// URL: /products?page=2
pageSize.value = 25
// URL: /products?size=25 (page reset to 1, removed from URL)
page.value = 1
// URL: /products?size=25 (page=1 removed as it's the default)
Page Size Reset
When changing page size, the page automatically resets to 1 to avoid out-of-bounds errors:
// User is on page 5 with 10 items per page
page.value = 5
pageSize.value = 10
// User changes to 50 items per page
pageSize.value = 50
// page automatically becomes 1
Integration with Backend
Your API endpoint should accept page and size query parameters:
export default defineEventHandler(async (event) => {
const query = getValidatedQuery(event, paginationSchema.parse)
const { page = 1, size = 10 } = query
const offset = (page - 1) * size
const items = await db.query.items.findMany({
limit: size,
offset,
})
const total = await db.select({ count: sql`count(*)` })
.from(items)
.then(res => Number(res[0].count))
return {
items,
total,
page,
size,
}
})
Notes
useParsedQuery internally to validate and parse URL query parameters, ensuring type safety and preventing invalid values.page and pageSize are reactive computed properties with getters and setters. This allows them to work seamlessly with v-model directives on Nuxt UI components.
