[{"data":1,"prerenderedAt":490},["ShallowReactive",2],{"en-post-\u002Fen\u002Fnuxt-useasyncdata-cache-error":3},{"id":4,"title":5,"body":6,"description":480,"extension":481,"meta":482,"navigation":484,"path":485,"seo":486,"sitemap":487,"stem":488,"__hash__":489},"blogEn\u002Fen\u002Fnuxt-useasyncdata-cache-error.md","Nuxt 3 useAsyncData Caching Bug: Why Every Post Shows the Same Content",{"type":7,"value":8,"toc":463},"minimark",[9,13,17,35,38,41,50,56,61,152,156,172,176,198,201,205,216,218,222,229,312,316,344,348,351,353,357,360,366,374,382,388,394,397,416,418,422,437,441,459],[10,11,5],"h1",{"id":12},"nuxt-3-useasyncdata-caching-bug-why-every-post-shows-the-same-content",[14,15,16],"p",{},"While building a blog detail page in Nuxt 3, I hit a confusing bug:",[18,19,20,24,32],"ol",{},[21,22,23],"li",{},"Open blog post #1.",[21,25,26,27,31],{},"Go back (via ",[28,29,30],"code",{},"NuxtLink",").",[21,33,34],{},"Click into blog post #2 — and post #1's content is still shown.",[14,36,37],{},"The URL changes correctly. The content doesn't. This turned out to be a caching problem, not a routing one.",[39,40],"hr",{},[42,43,45,46,49],"h2",{"id":44},"root-cause-useasyncdatas-key-based-cache","Root cause: ",[28,47,48],{},"useAsyncData","'s key-based cache",[14,51,52,53,55],{},"Nuxt 3's ",[28,54,48],{}," caches results internally to avoid redundant network requests — and that's exactly what bit me here.",[57,58,60],"h3",{"id":59},"the-original-code","The original code",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-javascript shiki shiki-themes github-light github-dark","const { data: post } = await useAsyncData(() => {\n  return queryCollection(\"blog\").path(route.path).first();\n});\n","javascript","",[28,69,70,116,146],{"__ignoreMap":67},[71,72,75,79,83,87,90,94,97,100,103,107,110,113],"span",{"class":73,"line":74},"line",1,[71,76,78],{"class":77},"szBVR","const",[71,80,82],{"class":81},"sVt8B"," { ",[71,84,86],{"class":85},"s4XuR","data",[71,88,89],{"class":81},": ",[71,91,93],{"class":92},"sj4cs","post",[71,95,96],{"class":81}," } ",[71,98,99],{"class":77},"=",[71,101,102],{"class":77}," await",[71,104,106],{"class":105},"sScJk"," useAsyncData",[71,108,109],{"class":81},"(() ",[71,111,112],{"class":77},"=>",[71,114,115],{"class":81}," {\n",[71,117,119,122,125,128,132,134,137,140,143],{"class":73,"line":118},2,[71,120,121],{"class":77},"  return",[71,123,124],{"class":105}," queryCollection",[71,126,127],{"class":81},"(",[71,129,131],{"class":130},"sZZnC","\"blog\"",[71,133,31],{"class":81},[71,135,136],{"class":105},"path",[71,138,139],{"class":81},"(route.path).",[71,141,142],{"class":105},"first",[71,144,145],{"class":81},"();\n",[71,147,149],{"class":73,"line":148},3,[71,150,151],{"class":81},"});\n",[57,153,155],{"id":154},"the-key-is-fixed","The key is fixed",[14,157,158,159,161,162,166,167,171],{},"When you don't pass an explicit key, ",[28,160,48],{}," derives one ",[163,164,165],"strong",{},"from the callback's source code itself"," — not from anything dynamic like the route. So every call site using this exact code shares the ",[168,169,170],"em",{},"same"," cache key, and Nuxt reuses whatever was cached under it.",[57,173,175],{"id":174},"what-actually-happens","What actually happens",[18,177,178,189],{},[21,179,180,181,184,185,188],{},"Visiting ",[28,182,183],{},"\u002Fblog\u002F1"," resolves ",[28,186,187],{},"queryCollection(\"blog\").path(\"\u002Fblog\u002F1\")"," and caches the result under that one fixed key.",[21,190,180,191,194,195,197],{},[28,192,193],{},"\u002Fblog\u002F2"," uses the ",[168,196,170],{}," key — so Nuxt just returns the cached result from step 1 instead of fetching again.",[14,199,200],{},"The URL updates. The rendered post does not.",[57,202,204],{"id":203},"summary-of-the-mechanism","Summary of the mechanism",[206,207,208,213],"ul",{},[21,209,210,212],{},[28,211,48],{}," caches by key.",[21,214,215],{},"Same key → no new fetch, cached data returned as-is.",[39,217],{},[42,219,221],{"id":220},"the-fix-make-the-key-dynamic","The fix: make the key dynamic",[14,223,224,225,228],{},"The fix is to derive the key from ",[28,226,227],{},"route.path",", so each route gets its own cache entry.",[62,230,232],{"className":64,"code":231,"language":66,"meta":67,"style":67},"const { data: post, error, refresh } = await useAsyncData(`blog-post-${route.path}`, () => {\n  return queryCollection(\"blog\").path(route.path).first();\n});\n",[28,233,234,288,308],{"__ignoreMap":67},[71,235,236,238,240,242,244,246,249,252,254,257,259,261,263,265,267,270,273,276,278,281,284,286],{"class":73,"line":74},[71,237,78],{"class":77},[71,239,82],{"class":81},[71,241,86],{"class":85},[71,243,89],{"class":81},[71,245,93],{"class":92},[71,247,248],{"class":81},", ",[71,250,251],{"class":92},"error",[71,253,248],{"class":81},[71,255,256],{"class":92},"refresh",[71,258,96],{"class":81},[71,260,99],{"class":77},[71,262,102],{"class":77},[71,264,106],{"class":105},[71,266,127],{"class":81},[71,268,269],{"class":130},"`blog-post-${",[71,271,272],{"class":81},"route",[71,274,275],{"class":130},".",[71,277,136],{"class":81},[71,279,280],{"class":130},"}`",[71,282,283],{"class":81},", () ",[71,285,112],{"class":77},[71,287,115],{"class":81},[71,289,290,292,294,296,298,300,302,304,306],{"class":73,"line":118},[71,291,121],{"class":77},[71,293,124],{"class":105},[71,295,127],{"class":81},[71,297,131],{"class":130},[71,299,31],{"class":81},[71,301,136],{"class":105},[71,303,139],{"class":81},[71,305,142],{"class":105},[71,307,145],{"class":81},[71,309,310],{"class":73,"line":148},[71,311,151],{"class":81},[57,313,315],{"id":314},"what-changed","What changed",[206,317,318,324],{},[21,319,320,321,275],{},"The key is now explicit and route-specific: ",[28,322,323],{},"blog-post-${route.path}",[21,325,326,327],{},"Every time the path changes, Nuxt sees a new key and fetches fresh data instead of reusing the cache.\n",[206,328,329,337],{},[21,330,331,333,334],{},[28,332,183],{}," → key: ",[28,335,336],{},"blog-post-\u002Fblog\u002F1",[21,338,339,333,341],{},[28,340,193],{},[28,342,343],{},"blog-post-\u002Fblog\u002F2",[57,345,347],{"id":346},"result","Result",[14,349,350],{},"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.",[39,352],{},[42,354,356],{"id":355},"why-this-matters-more-once-you-deploy","Why this matters more once you deploy",[14,358,359],{},"In production, Nuxt 3's SSR\u002Fprerendering 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.",[14,361,362,365],{},[163,363,364],{},"Before the fix"," — one shared payload regardless of path:",[62,367,372],{"className":368,"code":370,"language":371},[369],"language-text","\u002F_nuxt\u002Fdata\u002Fblog.json   \u002F\u002F reused for every route\n","text",[28,373,370],{"__ignoreMap":67},[14,375,376,378,379,381],{},[28,377,193],{}," would serve ",[28,380,183],{},"'s cached payload.",[14,383,384,387],{},[163,385,386],{},"After the fix"," — one payload per path:",[62,389,392],{"className":390,"code":391,"language":371},[369],"\u002F_nuxt\u002Fdata\u002Fblog-post-\u002Fblog\u002F1.json\n\u002F_nuxt\u002Fdata\u002Fblog-post-\u002Fblog\u002F2.json\n",[28,393,391],{"__ignoreMap":67},[14,395,396],{},"To confirm this in your own deployment, check:",[18,398,399,406,413],{},[21,400,401,402,405],{},"The generated payload files under ",[28,403,404],{},"_nuxt\u002Fdata\u002F"," — one per path, not one shared file.",[21,407,408,409,412],{},"The prerendered HTML under ",[28,410,411],{},".output\u002Fpublic\u002F"," — each route should have its own file with the correct content baked in.",[21,414,415],{},"Your hosting provider's request logs — each path should be served its own correct payload\u002FHTML, not a stale copy.",[39,417],{},[42,419,421],{"id":420},"takeaway","Takeaway",[14,423,424,426,427,248,429,432,433,436],{},[28,425,48],{},"'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 (",[28,428,227],{},[28,430,431],{},"route.params",", a dynamic prop), ",[163,434,435],{},"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.",[57,438,440],{"id":439},"references","References",[206,442,443,452],{},[21,444,445],{},[446,447,451],"a",{"href":448,"rel":449},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fapi\u002Fcomposables\u002Fuse-async-data",[450],"nofollow","Nuxt 3 Documentation: useAsyncData",[21,453,454],{},[446,455,458],{"href":456,"rel":457},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fintroduction",[450],"Nuxt 3: Caching and Prerendering",[460,461,462],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":67,"searchDepth":118,"depth":118,"links":464},[465,472,476,477],{"id":44,"depth":118,"text":466,"children":467},"Root cause: useAsyncData's key-based cache",[468,469,470,471],{"id":59,"depth":148,"text":60},{"id":154,"depth":148,"text":155},{"id":174,"depth":148,"text":175},{"id":203,"depth":148,"text":204},{"id":220,"depth":118,"text":221,"children":473},[474,475],{"id":314,"depth":148,"text":315},{"id":346,"depth":148,"text":347},{"id":355,"depth":118,"text":356},{"id":420,"depth":118,"text":421,"children":478},[479],{"id":439,"depth":148,"text":440},"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).","md",{"date":483},"2026.10.07",true,"\u002Fen\u002Fnuxt-useasyncdata-cache-error",{"title":5,"description":480},{"loc":485},"en\u002Fnuxt-useasyncdata-cache-error","VTv8oGo7NafB4mxQAma-r1WGNN9I3D5qqRo1RT-u8to",1791347386895]