Repository pattern + useAsyncData + Pinia store, hydration + caching problems?
We follow this article to write all our API calls https://medium.com/@luizzappa/nuxt-3-repository-pattern-organising-and-managing-your-calls-to-apis-with-typescript-acd563a4e046
so we can fetch a resource like so
const { data: documents } = await $api.users.getUploadedDocuments(); but we actually populate the reactive state in a pinia store, persisted in Cookie, const documentList = ref<DocumentMetadata[]>([]);, we use this documentList ref in our components. However we notice random cached requests, random network requests and sometimes double hydration from certain requests too, what to do?Medium
Nuxt 3 | Repository pattern: organising and managing your calls to ...
In Nuxt 2 the use of axios was straightforward and allowed a good management of API calls. When I started to develop with Nuxt 3 I found it…
2 Replies
Hi @td2t. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
You’re hitting known Nuxt 3 “gotchas” around
useAsyncData/useFetch caching + side effects + Pinia.
From the sources:
* useFetch is literally useAsyncData + $fetch, with built‑in caching keyed by the key argument and request parameters. [Which to use]
* This cache means:
* Nuxt may not re‑fetch if it thinks the call is the “same” (same key, same URL/params). [Nuxt shows old data]
* Forcing a unique key (e.g. Date.now()) disables caching and always re‑fetches. [Nuxt shows old data]
* Recent PRs improved client‑side getCachedData so cached data is reused beyond hydration unless you manually refresh. [getCachedData cache]
* useAsyncData is not intended for side effects like mutating a Pinia store; this can cause repeated executions and unexpected behaviour when the result is nullish/undefined. [useAsyncData side effects docs]
* Nuxt core explicitly optimises to avoid re‑running a handler when another useAsyncData with the same key is already active, and to consistently share cached data across instances. [don’t rerun asyncdata; respect cachedData]
* There’s also work on controlling execution (ready, immediate: false, reacting to key changes, etc.), which again assumes you’re using it as a pure data fetch, not as a place for arbitrary side effects. [ready option; key + immediate false]
Given that, your current pattern:
…is very likely:
* depending on useAsyncData/useFetch cache semantics and on store side effects at the same time, and
* being called from multiple places/components during SSR + hydration.
That combination perfectly explains “random”:
* cached vs network requests (Nuxt’s cache rules + timing),
* double hydration or apparently duplicated calls (SSR + client, multiple components),
* inconsistent store state (side effects re‑run when Nuxt decides to refetch).
Recommendations based on the sources
1. Stop using useAsyncData/useFetch as a driver for Pinia side effects
The Nuxt docs PR is explicit that this usage is problematic: useAsyncData is for fetching data, not for triggering store mutations. [useAsyncData side effects docs]
Instead:
* Fetch data with useAsyncData/useFetch in the component and bind it directly, or
* Fetch in the Pinia store using a plain $fetch/$fetch.raw (no asyncData), then assign to documentList.
In other words, either:
or:
but don’t use useAsyncData inside the store and then also rely on its caching.
2. Control or disable caching when you really want fresh data
If a particular fetch must always hit the network (e.g. user‑specific, mutable data), give useFetch/useAsyncData a changing key so Nuxt doesn’t reuse cache. [Nuxt shows old data]
This is crude but effective for debugging whether the cache is the root cause.
3. Use cached data intentionally, not implicitly
Newer Nuxt behaviour returns cached data for a given asyncData key as long as there’s no manual refresh, including beyond hydration. [getCachedData cache]
That means:
* If you want shared, cached data across components, keep using the same key and don’t mutate global store state from inside the handler; just consume data directly from useAsyncData/useFetch.
* If you want per‑usage fetches or per‑user data, either:
* vary the key (e.g. include user id), or
* skip asyncData caching entirely and use plain $fetch inside your repository.
4. Avoid mixing “repository pattern” composables with Pinia persistence + asyncData caching
The Medium article pattern isn’t in our knowledge sources, but from what you described, your $api functions probably wrap useAsyncData/useFetch and then your store persists results in cookies.
Based on Nuxt’s own guidance on useAsyncData side effects and caching, the safer split is:
* Repository composables: small wrappers around plain $fetch only.
* useAsyncData / useFetch: used at the component level (or composables that are purely about fetching), returning their data to the caller without mutating global state.
* Pinia: owns state and persistence; actions call repository methods ($fetch) and assign results.
That removes the triangle of “asyncData cache ↔ repository ↔ store side effects” which is the main source of the randomness you’re seeing.
---
The knowledge sources don’t give a one‑line “fix this repository pattern” recipe, but they clearly point to:
* useAsyncData/useFetch should be treated as pure data fetchers with caching semantics.
* Avoid putting Pinia mutations / side effects inside them.
* When behaviour looks random, first eliminate asyncData caching from the equation (unique keys or plain $fetch), then reintroduce caching where you explicitly want it.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@413148786836373544> Kapa.ai is still learning and improving, please let me know how I did by reacting below.