I am experiencing a pagination caching issue in Nuxt 3 while hosting on Netlify. When I navigate from page 2 of a category to another category and then return, the previous page (2) is retained, instead of resetting to page 1.
You can try it here:
This issue only occurs in production, but works correctly in development. Expected Behavior: When switching categories, pagination should reset to page 1. When returning to a category, page 1 articles should be displayed, not a previously viewed page.
Steps to Reproduce:
- Visit category A, navigate to page 2.
- Switch to category B.
- Return to category A.
- The articles displayed are still from page 2, instead of resetting to page 1.
Relevant Code:
<script lang="ts" setup>
import { ref, computed, watch, nextTick } from 'vue'
import { useRoute, useRouter, useAsyncData, useSeoMeta, createError } from '#app'
const route = useRoute()
const router = useRouter()
const category = route.params.category as string
const page = ref(parseInt(route.query.page as string) || 1)
const itemsPerPage = 9
// Watch for changes in the page and update the URL
watch(() => route.query.page, async (newPage) => {
page.value = parseInt(newPage as string) || 1
await refresh()
// Ensure scrolling happens after the DOM updates
await nextTick()
window.scrollTo({ top: 0, behavior: 'smooth' })
})
// Fetch the latest featured article separately
const { data: firstArticle } = await useAsyncData(`content:category:${category}:featured`, async () => {
const [article] = await queryContent('/articles')
.where({ categories: { $contains: category }, published: { $ne: false } })
.sort({ publishedAt: -1 })
.limit(1)
.find()
return article || null
})
// Fetch total number of articles excluding the featured one
const { data: total } = await useAsyncData(`content:category:${category}:total`, async () => {
const count = await queryContent('/articles')
.where({
categories: { $contains: category },
published: { $ne: false },
_path: { $ne: firstArticle.value?._path } // Exclude featured article
})
.count()
return count
})
// Fetch paginated articles while skipping the featured article
const { data: remainingArticles, refresh } = await useAsyncData(
`content:category:${category}:page:${page.value}`,
async () => {
return queryContent('/articles')
.where({
categories: { $contains: category },
published: { $ne: false },
_path: { $ne: firstArticle.value?._path } // Exclude featured article
})
.only(['_path', 'title', 'categories', 'description', 'publishedAt', 'image', 'authors'])
.sort({ publishedAt: -1 })
.limit(itemsPerPage)
.skip(itemsPerPage * (page.value - 1)) // Ensure pagination logic still works
.find()
}
)
if (!remainingArticles.value) {
throw createError({
statusCode: 404,
message: 'Category not found',
})
}
const appConfig = useAppConfig()
const formattedCategory = formatCategory(category)
const title = `${formattedCategory} - ${appConfig.app.name}`
const categoryDescriptions = appConfig.categories
const description = categoryDescriptions[formattedCategory] || `All articles related to ${formattedCategory}`
const runtimeConfig = useRuntimeConfig()
useSeoMeta({
title: title,
description,
ogTitle: title,
ogDescription: description,
ogImage: runtimeConfig.app.url + '/images/the_fineprint.jpeg',
twitterTitle: title,
twitterDescription: description,
twitterImage: runtimeConfig.app.url + '/images/the_fineprint.jpeg',
twitterCard: 'summary',
})
</script>
<template>
<UContainer>
<UPage v-if="remainingArticles">
<UPageHeader
headline="Category"
:title="formattedCategory"
:ui="{ headline: 'text-primary-900 dark:text-white', description: 'text-primary-900 dark:text-white' }"
/>
<UPageBody v-if="firstArticle">
<!-- Featured Article (First Article) -->
<div class="group relative grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8 items-center xl:items-start">
<div class="md:col-start-1 md:row-start-1 flex flex-col">
<h3 class="text-xl font-semibold text-primary-900 dark:text-white">
<NuxtLink :to="firstArticle._path">
<span class="absolute inset-0 z-10" />
<span>{{ firstArticle.title }}</span>
</NuxtLink>
</h3>
<p class="mt-4 text-primary-500 text-primary-900 dark:text-white">
{{ firstArticle.description }}
</p>
<dl
v-if="firstArticle.publishedAt || firstArticle.authors"
class="mt-6 flex justify-between items-center text-sm text-primary-900 dark:text-white"
>
<template v-if="firstArticle.authors">
<dt class="sr-only">
Author
</dt>
<dd>
<ArticleCardAuthors :authors="firstArticle.authors" />
</dd>
</template>
<template v-if="firstArticle.publishedAt">
<dt class="sr-only">
Published at
</dt>
<dd>
<ArticleCardDate :date="firstArticle.publishedAt" />
</dd>
</template>
</dl>
</div>
<div class="overflow-hidden rounded-md row-start-1 md:col-start-2 xl:col-span-2">
<!-- Do not use NuxtImg to avoid breaking images. -->
<img
v-if="firstArticle.image"
:src="firstArticle.image.src"
:alt="firstArticle.image.alt"
class="aspect-[16/9] object-cover transition-transform transform ease-in duration-300 group-hover:scale-[102%]"
aria-hidden="true"
>
</div>
</div>
<UPageGrid class="mt-8">
<ArticleCard
v-for="article in remainingArticles"
:key="article._path"
:to="article._path!"
:title="article.title!"
:description="article.description"
:date="article.publishedAt"
:image="article.image"
:authors="article.authors"
/>
</UPageGrid>
</UPageBody>
<!-- Pagination Component -->
<UPagination
v-if="total > itemsPerPage"
:total="total"
:page-count="itemsPerPage"
v-model="page"
:to="(page: number) => ({
query: { page },
hash: '#links'
})"
/>
</UPage>
</UContainer>
</template>
I am struggling for days now I can’t get it to work. Help is really appreciated