Nuxt 3 useAsyncData Caching Bug: Why Every Post Shows the Same Content
Clicking a second blog post still renders the first one's data — a walkthrough of useAsyncData's default key-based caching and the one-line fix (a dynamic key).
Nuxt 3 useAsyncData Caching Bug: Why Every Post Shows the Same Content
While building a blog detail page in Nuxt 3, I hit a confusing bug:
- Open blog post #1.
- Go back (via NuxtLink).
- Click into blog post #2 — and post #1's content is still shown.
The URL changes correctly. The content doesn't. This turned out to be a caching problem, not a routing one.
Root cause: useAsyncData's key-based cache
Nuxt 3's useAsyncData caches results internally to avoid redundant network requests — and that's exactly what bit me here.
The original code
const { data: post } = await useAsyncData(() => {
return queryCollection("blog").path(route.path).first();
});
The key is fixed
When you don't pass an explicit key, useAsyncData derives one from the callback's source code itself — not from anything dynamic like the route. So every call site using this exact code shares the same cache key, and Nuxt reuses whatever was cached under it.
What actually happens
- Visiting /blog/1 resolves queryCollection("blog").path("/blog/1") and caches the result under that one fixed key.
- Visiting /blog/2 uses the same key — so Nuxt just returns the cached result from step 1 instead of fetching again.
The URL updates. The rendered post does not.
Summary of the mechanism
- useAsyncData caches by key.
- Same key → no new fetch, cached data returned as-is.
The fix: make the key dynamic
The fix is to derive the key from route.path, so each route gets its own cache entry.
const { data: post, error, refresh } = await useAsyncData(`blog-post-${route.path}`, () => {
return queryCollection("blog").path(route.path).first();
});
What changed
- The key is now explicit and route-specific: blog-post-${route.path}.
- Every time the path changes, Nuxt sees a new key and fetches fresh data instead of reusing the cache.
- /blog/1 → key: blog-post-/blog/1
- /blog/2 → key: blog-post-/blog/2
Result
Each route now gets its own cached entry, so navigating between posts always fetches (or correctly reuses) the right data — the stale-content bug disappears.
Why this matters more once you deploy
In production, Nuxt 3's SSR/prerendering pipeline adds another layer: payload JSON files generated per cache key. A fixed key means every route shares the same payload file; a dynamic key gives each route its own.
Before the fix — one shared payload regardless of path:
/_nuxt/data/blog.json // reused for every route
/blog/2 would serve /blog/1's cached payload.
After the fix — one payload per path:
/_nuxt/data/blog-post-/blog/1.json
/_nuxt/data/blog-post-/blog/2.json
To confirm this in your own deployment, check:
- The generated payload files under _nuxt/data/ — one per path, not one shared file.
- The prerendered HTML under .output/public/ — each route should have its own file with the correct content baked in.
- Your hosting provider's request logs — each path should be served its own correct payload/HTML, not a stale copy.
Takeaway
useAsyncData's default (code-derived) key is a common trap on any page that's reused across multiple routes. Whenever the fetch itself depends on something route-specific (route.path, route.params, a dynamic prop), always pass an explicit, route-aware key — it's a one-line fix, but it's the difference between correct data and silently stale content.