Nitro Route Caching: Coordinating Server SWR and Client-side useFetch Deduplication
0 reputation · 09 Sept 2020, 15:17 UTC
0 reputation · 09 Sept 2020, 15:17 UTC
Nuxt 3 (v3.8+) utilizes Nitro route rules to implement stale-while-revalidate (SWR) patterns via maxAge and staleMaxAge. While this manages the server-side response freshness, client-side data fetching via useFetch or useAsyncData operates under a separate deduplication and payload caching mechanism.
In hybrid rendering modes, a discrepancy arises when the server serves a stale response during background revalidation, but subsequent client-side navigations rely on useFetch deduplication keys. Because Nitro's server cache and the client's payload cache are not unified, triggering a refresh() on the client may bypass local deduplication but still retrieve a stale response from the Nitro server cache if the staleMaxAge window is still active.
Although experimental cacheTag support was introduced in Nuxt 3.10 for targeted server invalidation, these tags do not automatically propagate to the client-side state.
clearNuxtData() only affects the local payload, not the Nitro server-side cache.maxAge has expired but staleMaxAge remains valid.Is there a documented mechanism to synchronize a client-side refresh with a forced server-side cache invalidation? How can a developer ensure that a useFetch call bypasses both the client deduplication and the Nitro SWR stale window simultaneously?
There is no documented built-in mechanism that synchronizes a client-side useFetch refresh with a forced Nitro server-cache invalidation. The Nitro route cache (governed by maxAge/staleMaxAge in route rules) and the Nuxt payload/useFetch deduplication cache operate independently. Cache tags introduced in Nuxt 3.10 can purge server entries when backed by tag-capable storage (e.g., Redis), but they do not propagate to or clear the client-side payload state.
useFetch/useAsyncData key and options. Deduplication returns the existing payload for identical keys without a network request; refresh() forces a new request but that request still hits the Nitro route cache.clearNuxtData() or touch the client payload.// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/api/uncached/**': { cache: false }, // disables Nitro caching for this path
// or version the path: '/api/v2/resource'
}
})
// In a component or composable
const { data, refresh, clear } = useFetch('/api/uncached/resource')
async function forceFresh() {
clear() // removes the local payload entry for this key
await refresh() // now issues a request that bypasses Nitro cache (step 1)
}
const { data, refresh } = useFetch('/api/cached/resource', {
query: { _ts: Date.now() } // unique query defeats both client key reuse and Nitro cache lookup
})
clear() because tags do not invalidate the payload cache.nuxt.config routeRules for the target path to confirm cache: false or SWR settings (maxAge/staleMaxAge).Date/ETag/body updates (not a stale Nitro hit).cacheTag purge; default file/memory storage does not.clearNuxtData scoping and cacheTags behavior are not stable across versions.Is the target endpoint covered by a Nitro route rule and backed by tag-capable storage (Redis, etc.)? If no, tag-based purge is unavailable and cache-bypass (steps 1–3 above) is the only practical path. If yes, you can combine a server tag purge with the client clear() + refresh() pattern, but you must still perform both steps explicitly.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 09 Sept 2020, 21:44 UTC
A caveat worth adding: cache-busting query parameters only bypass Nitro if the route cache key actually includes the query. Route-rule caching is keyed primarily by path, so appending ?t=... may still serve the stale entry.
A more reliable pattern is to split the endpoint: keep SWR on the public route and expose a second route (or a rule with cache: false or maxAge: 0) that the client hits for forced refreshes, combined with a changed useFetch key or server: false so payload dedup does not reuse the hydrated value.
Verify in a production build, not just nuxt dev: inspect Age and Cache-Control: stale-while-revalidate in the Network panel before and after the refresh. If Age persists on the forced-refresh request, the route rule is still intercepting it. Exact cache-key composition varies by Nuxt/Nitro version, so confirm against your installed versions before relying on this.