[{"data":1,"prerenderedAt":1185},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query":1176},{"id":4,"title":5,"body":6,"description":1157,"extension":1158,"meta":1159,"navigation":1169,"path":1170,"seo":1171,"stem":1174,"__hash__":1175},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query\u002Findex.md","Stale-While-Revalidate Data Fetching with SWR and TanStack Query",{"type":7,"value":8,"toc":1140},"minimark",[9,13,37,40,187,192,225,229,238,248,258,264,342,346,351,588,591,595,669,673,682,686,794,797,920,924,927,931,938,942,960,964,994,998,1008,1014,1020,1026,1030,1042,1051,1060,1069,1085,1098,1102,1125,1130,1133,1136],[10,11,5],"h1",{"id":12},"stale-while-revalidate-data-fetching-with-swr-and-tanstack-query",[14,15,16,17,22,23,27,28,32,33,36],"p",{},"This guide brings the pattern from ",[18,19,21],"a",{"href":20},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002F","Stale-While-Revalidate Implementation"," into the browser's data layer, within ",[18,24,26],{"href":25},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". HTTP ",[29,30,31],"code",{},"stale-while-revalidate"," lets caches serve a stale response while refreshing it in the background. Client data libraries — SWR and TanStack Query for React (with ports for Vue, Svelte and Solid) — apply the same idea to application data: when a component needs ",[29,34,35],{},"\u002Fapi\u002Fproducts?cat=shoes",", the library returns the cached result immediately if it has one, renders, and refetches in the background, updating the UI if the data changed.",[14,38,39],{},"The performance effect is largest on route transitions in single-page apps. Without client caching, navigating back to a list re-fetches and shows a loading state; with SWR semantics, the list appears instantly from cache and quietly refreshes. The risks are in configuration: refetch-on-focus and refetch-on-mount defaults can multiply API traffic, and a cache that is never considered fresh refetches on every render.",[14,41,42],{},[43,44,50,51,50,58,50,62,50,65,50,83,50,91,50,97,50,104,50,111,50,114,50,118,50,121,50,125,50,134,50,137,50,140,50,146,50,152,50,157,50,161,50,165,50,170,50,175,50,179,50,183,50],"svg",{"viewBox":45,"width":46,"role":47,"ariaLabel":48,"style":49},"0 0 760 330","100%","img","Sequence of a component reading cached data instantly, rendering it, and a background refetch updating the cache and UI.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[52,53],"rect",{"className":54,"x":56,"y":56,"width":46,"height":46,"fill":57},[55],"svg-canvas","0","#ffffff",[59,60,61],"title",{},"Stale-while-revalidate in the client",[63,64,48],"desc",{},[66,67,68],"defs",{},[69,70,77],"marker",{"id":71,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"fa6e4f914d","0 0 10 10","9","5","7","auto-start-reverse",[78,79],"path",{"d":80,"fill":81,"style":82},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[52,84],{"x":85,"y":85,"width":86,"height":87,"rx":88,"fill":89,"stroke":81,"style":90},"1","758","328","10","none","stroke-opacity:0.18",[92,93,61],"text",{"x":94,"y":95,"fill":81,"style":96},"28.0","34.0","font-size:16px;font-weight:700",[52,98],{"x":99,"y":100,"width":101,"height":95,"rx":102,"fill":81,"stroke":81,"style":103},"70.3","56.0","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[92,105,110],{"x":106,"y":107,"fill":81,"style":108,"textAnchor":109},"145.3","78.0","font-size:13px;font-weight:700","middle","Component",[52,112],{"x":113,"y":100,"width":101,"height":95,"rx":102,"fill":81,"stroke":81,"style":103},"305.0",[92,115,117],{"x":116,"y":107,"fill":81,"style":108,"textAnchor":109},"380.0","Query cache",[52,119],{"x":120,"y":100,"width":101,"height":95,"rx":102,"fill":81,"stroke":81,"style":103},"539.7",[92,122,124],{"x":123,"y":107,"fill":81,"style":108,"textAnchor":109},"614.7","API",[126,127],"line",{"x1":106,"y1":128,"x2":106,"y2":129,"stroke":81,"strokeWidth":130,"strokeDashArray":131,"style":133},"90.0","308.0","1.5",[132,132],"4","stroke-opacity:0.35",[126,135],{"x1":116,"y1":128,"x2":116,"y2":129,"stroke":81,"strokeWidth":130,"strokeDashArray":136,"style":133},[132,132],[126,138],{"x1":123,"y1":128,"x2":123,"y2":129,"stroke":81,"strokeWidth":130,"strokeDashArray":139,"style":133},[132,132],[126,141],{"x1":106,"y1":142,"x2":143,"y2":142,"stroke":81,"strokeWidth":130,"style":144,"markerEnd":145},"124.0","378.0","stroke-opacity:0.6","url(#fa6e4f914d)",[92,147,151],{"x":148,"y":149,"fill":81,"style":150,"textAnchor":109},"262.7","117.0","font-size:12px","useQuery(products)",[126,153],{"x1":116,"y1":154,"x2":155,"y2":154,"stroke":81,"strokeWidth":130,"strokeDashArray":156,"style":144,"markerEnd":145},"164.0","147.3",[74,132],[92,158,160],{"x":148,"y":159,"fill":81,"style":150,"textAnchor":109},"157.0","cached data (stale)",[126,162],{"x1":116,"y1":163,"x2":164,"y2":163,"stroke":81,"strokeWidth":130,"style":144,"markerEnd":145},"204.0","612.7",[92,166,169],{"x":167,"y":168,"fill":81,"style":150,"textAnchor":109},"497.3","197.0","background refetch",[126,171],{"x1":123,"y1":172,"x2":173,"y2":172,"stroke":81,"strokeWidth":130,"strokeDashArray":174,"style":144,"markerEnd":145},"244.0","382.0",[74,132],[92,176,178],{"x":167,"y":177,"fill":81,"style":150,"textAnchor":109},"237.0","fresh data",[126,180],{"x1":116,"y1":181,"x2":155,"y2":181,"stroke":81,"strokeWidth":130,"strokeDashArray":182,"style":144,"markerEnd":145},"284.0",[74,132],[92,184,186],{"x":148,"y":185,"fill":81,"style":150,"textAnchor":109},"277.0","re-render if changed",[188,189,191],"h2",{"id":190},"rapid-diagnosis","Rapid Diagnosis",[193,194,195,203,209,219],"ul",{},[196,197,198,202],"li",{},[199,200,201],"strong",{},"Watch the Network panel during navigation."," If returning to a previously visited view refetches and shows a spinner, there is no client cache (or it is configured as always-stale with loading states).",[196,204,205,208],{},[199,206,207],{},"Count requests per view."," Duplicate requests for the same key from several components indicate missing deduplication.",[196,210,211,214,215,218],{},[199,212,213],{},"Check refetch triggers."," Switching tabs and returning may trigger refetches of every active query (",[29,216,217],{},"refetchOnWindowFocus",").",[196,220,221,224],{},[199,222,223],{},"Look at loading states."," UIs that show spinners for data the app fetched seconds ago are not using cached data during revalidation.",[188,226,228],{"id":227},"root-cause-analysis","Root Cause Analysis",[14,230,231,50,234,237],{},[199,232,233],{},"1. Fetching in effects without a cache.",[29,235,236],{},"useEffect(() => fetch(...))"," patterns refetch on every mount and cannot share results between components.",[14,239,240,243,244,247],{},[199,241,242],{},"2. Zero staleTime everywhere."," TanStack Query's default ",[29,245,246],{},"staleTime"," is 0, meaning cached data is immediately stale; it is still shown instantly, but refetches happen on every mount and focus, multiplying API calls.",[14,249,250,253,254,257],{},[199,251,252],{},"3. Loading UI on background refetch."," Rendering spinners whenever ",[29,255,256],{},"isFetching"," is true discards the benefit of having cached data.",[14,259,260,263],{},[199,261,262],{},"4. No prefetching."," The first visit to each view always waits; prefetching on hover makes even that instant.",[14,265,266],{},[43,267,50,270,50,273,50,276,50,278,50,281,50,283,50,290,50,299,50,304,50,308,50,312,50,316,50,320,50,326,50,330,50,334,50,338,50],{"viewBox":268,"width":46,"role":47,"ariaLabel":269,"style":49},"0 0 760 194","Bar chart of API requests generated by a typical SPA session with different client data-fetching configurations.",[52,271],{"className":272,"x":56,"y":56,"width":46,"height":46,"fill":57},[55],[59,274,275],{},"API requests per 10-minute session by configuration",[63,277,269],{},[52,279],{"x":85,"y":85,"width":86,"height":280,"rx":88,"fill":89,"stroke":81,"style":90},"192",[92,282,275],{"x":94,"y":95,"fill":81,"style":96},[92,284,289],{"x":285,"y":286,"fill":81,"style":287,"textAnchor":288},"226.6","70.0","font-size:13px","end","Fetch in useEffect",[52,291],{"x":292,"y":100,"width":293,"height":294,"rx":295,"fill":296,"stroke":297,"style":298},"238.6","429.4","19","3","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[92,300,303],{"x":301,"y":286,"fill":81,"style":302},"674.0","font-size:12px;font-weight:600","84",[92,305,307],{"x":285,"y":306,"fill":81,"style":287,"textAnchor":288},"101.0","TanStack Query, staleTime 0",[52,309],{"x":292,"y":310,"width":311,"height":294,"rx":295,"fill":296,"stroke":297,"style":298},"87.0","311.8",[92,313,315],{"x":314,"y":306,"fill":81,"style":302},"556.4","61",[92,317,319],{"x":285,"y":318,"fill":81,"style":287,"textAnchor":288},"132.0","staleTime 60s, focus refetch off",[52,321],{"x":292,"y":322,"width":323,"height":294,"rx":295,"fill":324,"stroke":324,"style":325},"118.0","117.6","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[92,327,329],{"x":328,"y":318,"fill":81,"style":302},"362.2","23",[92,331,333],{"x":285,"y":332,"fill":81,"style":287,"textAnchor":288},"163.0","+ hover prefetch (more, but useful)",[52,335],{"x":292,"y":336,"width":337,"height":294,"rx":295,"fill":324,"stroke":324,"style":325},"149.0","138.0",[92,339,341],{"x":340,"y":332,"fill":81,"style":302},"382.6","27",[188,343,345],{"id":344},"step-by-step-resolution","Step-by-Step Resolution",[347,348,350],"h3",{"id":349},"_1-move-data-fetching-into-a-query-library","1. Move data fetching into a query library",[352,353,358],"pre",{"className":354,"code":355,"language":356,"meta":357,"style":357},"language-jsx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { useQuery } from '@tanstack\u002Freact-query';\nfunction ProductList({ category }) {\n  const { data, isPending } = useQuery({\n    queryKey: ['products', category],\n    queryFn: () => fetch(`\u002Fapi\u002Fproducts?cat=${category}`).then((r) => r.json()),\n    staleTime: 60_000,                     \u002F\u002F fresh for a minute: no refetch on remount\n  });\n  if (isPending) return \u003CListSkeleton \u002F>;\n  return \u003CGrid items={data} \u002F>;\n}\n\u002F\u002F trade-off: a 60s staleTime means a list can be up to a minute out of date on\n\u002F\u002F return. For volatile data (stock, prices), use a shorter value or invalidate\n\u002F\u002F on the mutations that change it.\n","jsx","",[29,359,360,382,402,433,445,496,512,518,540,564,570,576,582],{"__ignoreMap":357},[361,362,364,368,372,375,379],"span",{"class":126,"line":363},1,[361,365,367],{"class":366},"sPARh","import",[361,369,371],{"class":370},"saISM"," { useQuery } ",[361,373,374],{"class":366},"from",[361,376,378],{"class":377},"sZ8jY"," '@tanstack\u002Freact-query'",[361,380,381],{"class":370},";\n",[361,383,385,388,392,395,399],{"class":126,"line":384},2,[361,386,387],{"class":366},"function",[361,389,391],{"class":390},"smZ65"," ProductList",[361,393,394],{"class":370},"({ ",[361,396,398],{"class":397},"sQw3B","category",[361,400,401],{"class":370}," }) {\n",[361,403,405,408,411,415,418,421,424,427,430],{"class":126,"line":404},3,[361,406,407],{"class":366},"  const",[361,409,410],{"class":370}," { ",[361,412,414],{"class":413},"sPXB4","data",[361,416,417],{"class":370},", ",[361,419,420],{"class":413},"isPending",[361,422,423],{"class":370}," } ",[361,425,426],{"class":366},"=",[361,428,429],{"class":390}," useQuery",[361,431,432],{"class":370},"({\n",[361,434,436,439,442],{"class":126,"line":435},4,[361,437,438],{"class":370},"    queryKey: [",[361,440,441],{"class":377},"'products'",[361,443,444],{"class":370},", category],\n",[361,446,448,451,454,457,460,463,466,468,471,473,476,479,482,485,487,490,493],{"class":126,"line":447},5,[361,449,450],{"class":390},"    queryFn",[361,452,453],{"class":370},": () ",[361,455,456],{"class":366},"=>",[361,458,459],{"class":390}," fetch",[361,461,462],{"class":370},"(",[361,464,465],{"class":377},"`\u002Fapi\u002Fproducts?cat=${",[361,467,398],{"class":370},[361,469,470],{"class":377},"}`",[361,472,218],{"class":370},[361,474,475],{"class":390},"then",[361,477,478],{"class":370},"((",[361,480,481],{"class":397},"r",[361,483,484],{"class":370},") ",[361,486,456],{"class":366},[361,488,489],{"class":370}," r.",[361,491,492],{"class":390},"json",[361,494,495],{"class":370},"()),\n",[361,497,499,502,505,508],{"class":126,"line":498},6,[361,500,501],{"class":370},"    staleTime: ",[361,503,504],{"class":413},"60_000",[361,506,507],{"class":370},",                     ",[361,509,511],{"class":510},"sjfSM","\u002F\u002F fresh for a minute: no refetch on remount\n",[361,513,515],{"class":126,"line":514},7,[361,516,517],{"class":370},"  });\n",[361,519,521,524,527,530,533,537],{"class":126,"line":520},8,[361,522,523],{"class":366},"  if",[361,525,526],{"class":370}," (isPending) ",[361,528,529],{"class":366},"return",[361,531,532],{"class":370}," \u003C",[361,534,536],{"class":535},"sZBmE","ListSkeleton",[361,538,539],{"class":370}," \u002F>;\n",[361,541,543,546,548,551,554,557,559,562],{"class":126,"line":542},9,[361,544,545],{"class":366},"  return",[361,547,532],{"class":370},[361,549,550],{"class":535},"Grid",[361,552,553],{"class":413}," items",[361,555,556],{"class":366},"={",[361,558,414],{"class":370},[361,560,561],{"class":366},"}",[361,563,539],{"class":370},[361,565,567],{"class":126,"line":566},10,[361,568,569],{"class":370},"}\n",[361,571,573],{"class":126,"line":572},11,[361,574,575],{"class":510},"\u002F\u002F trade-off: a 60s staleTime means a list can be up to a minute out of date on\n",[361,577,579],{"class":126,"line":578},12,[361,580,581],{"class":510},"\u002F\u002F return. For volatile data (stock, prices), use a shorter value or invalidate\n",[361,583,585],{"class":126,"line":584},13,[361,586,587],{"class":510},"\u002F\u002F on the mutations that change it.\n",[14,589,590],{},"Expected outcome: revisiting the view renders instantly from cache; refetches happen at most once a minute.",[347,592,594],{"id":593},"_2-configure-global-defaults-deliberately","2. Configure global defaults deliberately",[352,596,600],{"className":597,"code":598,"language":599,"meta":357,"style":357},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","const queryClient = new QueryClient({\n  defaultOptions: { queries: { staleTime: 30_000, gcTime: 10 * 60_000, refetchOnWindowFocus: false, retry: 1 } },\n});\n\u002F\u002F trade-off: disabling focus refetching saves requests but leaves data stale\n\u002F\u002F after long absences. Enable it per query for data that matters (inbox, cart).\n","javascript",[29,601,602,621,654,659,664],{"__ignoreMap":357},[361,603,604,607,610,613,616,619],{"class":126,"line":363},[361,605,606],{"class":366},"const",[361,608,609],{"class":413}," queryClient",[361,611,612],{"class":366}," =",[361,614,615],{"class":366}," new",[361,617,618],{"class":390}," QueryClient",[361,620,432],{"class":370},[361,622,623,626,629,632,634,637,640,643,646,649,651],{"class":126,"line":384},[361,624,625],{"class":370},"  defaultOptions: { queries: { staleTime: ",[361,627,628],{"class":413},"30_000",[361,630,631],{"class":370},", gcTime: ",[361,633,88],{"class":413},[361,635,636],{"class":366}," *",[361,638,639],{"class":413}," 60_000",[361,641,642],{"class":370},", refetchOnWindowFocus: ",[361,644,645],{"class":413},"false",[361,647,648],{"class":370},", retry: ",[361,650,85],{"class":413},[361,652,653],{"class":370}," } },\n",[361,655,656],{"class":126,"line":404},[361,657,658],{"class":370},"});\n",[361,660,661],{"class":126,"line":435},[361,662,663],{"class":510},"\u002F\u002F trade-off: disabling focus refetching saves requests but leaves data stale\n",[361,665,666],{"class":126,"line":447},[361,667,668],{"class":510},"\u002F\u002F after long absences. Enable it per query for data that matters (inbox, cart).\n",[347,670,672],{"id":671},"_3-show-cached-data-during-background-refetch","3. Show cached data during background refetch",[14,674,675,676,678,679,681],{},"Render from ",[29,677,414],{}," whenever it exists, and show a subtle indicator for ",[29,680,256],{}," rather than a full loading state.",[347,683,685],{"id":684},"_4-prefetch-on-intent","4. Prefetch on intent",[352,687,689],{"className":597,"code":688,"language":599,"meta":357,"style":357},"\u003CLink to={`\u002Fc\u002F${slug}`} onMouseEnter={() => queryClient.prefetchQuery({\n  queryKey: ['products', slug], queryFn: () => fetchProducts(slug), staleTime: 60_000 })}>\n  {name}\n\u003C\u002FLink>\n\u002F\u002F trade-off: hover prefetching on dense menus can fire many requests; add a\n\u002F\u002F short hover delay, as with route chunk prefetching.\n",[29,690,691,732,765,775,784,789],{"__ignoreMap":357},[361,692,693,696,699,702,704,707,710,712,714,717,719,722,724,727,730],{"class":126,"line":363},[361,694,695],{"class":370},"\u003C",[361,697,698],{"class":535},"Link",[361,700,701],{"class":413}," to",[361,703,556],{"class":366},[361,705,706],{"class":377},"`\u002Fc\u002F${",[361,708,709],{"class":370},"slug",[361,711,470],{"class":377},[361,713,561],{"class":366},[361,715,716],{"class":413}," onMouseEnter",[361,718,556],{"class":366},[361,720,721],{"class":370},"() ",[361,723,456],{"class":366},[361,725,726],{"class":370}," queryClient.",[361,728,729],{"class":390},"prefetchQuery",[361,731,432],{"class":370},[361,733,734,737,739,742,745,747,749,752,755,757,760,762],{"class":126,"line":384},[361,735,736],{"class":370},"  queryKey: [",[361,738,441],{"class":377},[361,740,741],{"class":370},", slug], ",[361,743,744],{"class":390},"queryFn",[361,746,453],{"class":370},[361,748,456],{"class":366},[361,750,751],{"class":390}," fetchProducts",[361,753,754],{"class":370},"(slug), staleTime: ",[361,756,504],{"class":413},[361,758,759],{"class":370}," })",[361,761,561],{"class":366},[361,763,764],{"class":370},">\n",[361,766,767,770,773],{"class":126,"line":404},[361,768,769],{"class":366},"  {",[361,771,772],{"class":370},"name",[361,774,569],{"class":366},[361,776,777,780,782],{"class":126,"line":435},[361,778,779],{"class":370},"\u003C\u002F",[361,781,698],{"class":535},[361,783,764],{"class":370},[361,785,786],{"class":126,"line":447},[361,787,788],{"class":510},"\u002F\u002F trade-off: hover prefetching on dense menus can fire many requests; add a\n",[361,790,791],{"class":126,"line":498},[361,792,793],{"class":510},"\u002F\u002F short hover delay, as with route chunk prefetching.\n",[14,795,796],{},"Expected outcome: first visits to likely next views are also instant.",[14,798,799],{},[43,800,50,803,50,806,50,809,50,811,50,814,50,816,50,820,50,827,50,831,50,835,50,838,50,842,50,846,50,850,50,854,50,856,50,860,50,863,50,866,50,868,50,872,50,874,50,877,50,879,50,882,50,885,50,888,50,890,50,893,50,895,50,898,50,901,50,905,50,909,50,911,50,915,50,917,50],{"viewBox":801,"width":46,"role":47,"ariaLabel":802,"style":49},"0 0 760 260","Important TanStack Query and SWR options and their effect on freshness and request volume.",[52,804],{"className":805,"x":56,"y":56,"width":46,"height":46,"fill":57},[55],[59,807,808],{},"Key options and what they control",[63,810,802],{},[52,812],{"x":85,"y":85,"width":86,"height":813,"rx":88,"fill":89,"stroke":81,"style":90},"258",[92,815,808],{"x":94,"y":95,"fill":81,"style":96},[52,817],{"x":94,"y":100,"width":818,"height":819,"rx":56,"fill":81,"stroke":81,"style":103},"210.0","30.0",[92,821,826],{"x":822,"y":823,"fill":81,"style":824,"textAnchor":825},"38.0","75.5","font-size:12.5px;font-weight:700","start","Option",[52,828],{"x":829,"y":100,"width":830,"height":819,"rx":56,"fill":81,"stroke":81,"style":103},"238.0","247.0",[92,832,834],{"x":833,"y":823,"fill":81,"style":824,"textAnchor":109},"361.5","Controls",[52,836],{"x":837,"y":100,"width":830,"height":819,"rx":56,"fill":81,"stroke":81,"style":103},"485.0",[92,839,841],{"x":840,"y":823,"fill":81,"style":824,"textAnchor":109},"608.5","Typical value",[52,843],{"x":94,"y":844,"width":818,"height":845,"rx":56,"fill":89,"stroke":81,"style":133},"86.0","46.0",[92,847,849],{"x":822,"y":848,"fill":81,"style":824,"textAnchor":825},"105.5","staleTime (TQ) \u002F dedupingInterval",[92,851,853],{"x":822,"y":852,"fill":81,"style":824,"textAnchor":825},"121.5","(SWR)",[52,855],{"x":829,"y":844,"width":830,"height":845,"rx":56,"fill":89,"stroke":81,"style":133},[92,857,859],{"x":833,"y":858,"fill":81,"style":150,"textAnchor":109},"113.5","when cached data is considered fresh",[52,861],{"x":837,"y":844,"width":830,"height":845,"rx":56,"fill":324,"stroke":324,"style":862},"fill-opacity:0.14;stroke-opacity:0.9",[92,864,865],{"x":840,"y":858,"fill":81,"style":150,"textAnchor":109},"30-300s for lists",[52,867],{"x":94,"y":318,"width":818,"height":819,"rx":56,"fill":89,"stroke":81,"style":133},[92,869,871],{"x":822,"y":870,"fill":81,"style":824,"textAnchor":825},"151.5","gcTime (TQ)",[52,873],{"x":829,"y":318,"width":830,"height":819,"rx":56,"fill":89,"stroke":81,"style":133},[92,875,876],{"x":833,"y":870,"fill":81,"style":150,"textAnchor":109},"how long unused data stays cached",[52,878],{"x":837,"y":318,"width":830,"height":819,"rx":56,"fill":324,"stroke":324,"style":862},[92,880,881],{"x":840,"y":870,"fill":81,"style":150,"textAnchor":109},"5-30 min",[52,883],{"x":94,"y":884,"width":818,"height":819,"rx":56,"fill":89,"stroke":81,"style":133},"162.0",[92,886,217],{"x":822,"y":887,"fill":81,"style":824,"textAnchor":825},"181.5",[52,889],{"x":829,"y":884,"width":830,"height":819,"rx":56,"fill":89,"stroke":81,"style":133},[92,891,892],{"x":833,"y":887,"fill":81,"style":150,"textAnchor":109},"refetch when tab regains focus",[52,894],{"x":837,"y":884,"width":830,"height":819,"rx":56,"fill":81,"stroke":81,"style":103},[92,896,897],{"x":840,"y":887,"fill":81,"style":150,"textAnchor":109},"per query",[52,899],{"x":94,"y":900,"width":818,"height":845,"rx":56,"fill":89,"stroke":81,"style":133},"192.0",[92,902,904],{"x":822,"y":903,"fill":81,"style":824,"textAnchor":825},"211.5","keepPreviousData \u002F",[92,906,908],{"x":822,"y":907,"fill":81,"style":824,"textAnchor":825},"227.5","placeholderData",[52,910],{"x":829,"y":900,"width":830,"height":845,"rx":56,"fill":89,"stroke":81,"style":133},[92,912,914],{"x":833,"y":913,"fill":81,"style":150,"textAnchor":109},"219.5","show old data while new key loads",[52,916],{"x":837,"y":900,"width":830,"height":845,"rx":56,"fill":324,"stroke":324,"style":862},[92,918,919],{"x":840,"y":913,"fill":81,"style":150,"textAnchor":109},"for pagination",[188,921,923],{"id":922},"verification","Verification",[14,925,926],{},"Navigate between two views several times with the Network panel open: the second and later visits should render instantly and issue at most one background request per stale query. Count API requests per session in RUM or server logs before and after; they should fall even with prefetching enabled. Measure route transition time with User Timing; cached views should transition in a single frame.",[188,928,930],{"id":929},"worked-example-a-dashboard-spa","Worked Example: A Dashboard SPA",[14,932,933,934,937],{},"An analytics dashboard fetched widget data in effects; switching between three dashboards re-fetched 40 widgets each time, with loading spinners on every switch and API p95 latency spiking under load. Migrating to TanStack Query with ",[29,935,936],{},"staleTime: 120_000",", focus refetching only for alert widgets, and prefetching on dashboard-tab hover made switches instant after the first visit and cut API traffic by 64%. INP for tab switches improved too, because rendering from cache avoided two state updates (loading, then loaded) per widget.",[188,939,941],{"id":940},"server-rendering-and-hydrating-the-cache","Server Rendering and Hydrating the Cache",[14,943,944,945,948,949,952,953,956,957,959],{},"In SSR frameworks, fetch data on the server and pass it to the client cache (TanStack Query's ",[29,946,947],{},"dehydrate","\u002F",[29,950,951],{},"HydrationBoundary",", SWR's ",[29,954,955],{},"fallback","). The client then starts with fresh data and does not refetch immediately on hydration — avoiding the double fetch that commonly doubles API load in SSR apps. Set ",[29,958,246],{}," above zero so hydration does not trigger an immediate background refetch of everything the server just fetched.",[188,961,963],{"id":962},"common-mistakes","Common Mistakes",[193,965,966,972,978,988],{},[196,967,968,971],{},[199,969,970],{},"Unstable query keys."," Keys built from objects with changing identity cause cache misses; use serialisable, stable keys.",[196,973,974,977],{},[199,975,976],{},"Loading states on refetch."," Users see spinners for data they already have.",[196,979,980,983,984,987],{},[199,981,982],{},"Forgetting invalidation after mutations."," Call ",[29,985,986],{},"invalidateQueries"," for affected keys so stale data does not linger after the user changes it.",[196,989,990,993],{},[199,991,992],{},"Caching personal data across users."," Clear the query cache on logout.",[188,995,997],{"id":996},"edge-cases","Edge Cases",[14,999,1000,1003,1004,1007],{},[199,1001,1002],{},"Pagination."," Use ",[29,1005,1006],{},"placeholderData: keepPreviousData"," so moving between pages does not blank the list.",[14,1009,1010,1013],{},[199,1011,1012],{},"Infinite queries."," Refetching an infinite list refetches every page; cap pages or refetch only the first.",[14,1015,1016,1019],{},[199,1017,1018],{},"Offline."," Persist the query cache to IndexedDB for offline-capable apps, with versioning so schema changes do not crash old caches.",[14,1021,1022,1025],{},[199,1023,1024],{},"Request deduplication."," Both libraries dedupe concurrent requests for the same key; make sure components share keys rather than inventing variants.",[188,1027,1029],{"id":1028},"faq","FAQ",[1031,1032,1035,1039],"details",{"className":1033},[1034],"faq-item",[1036,1037,1038],"summary",{},"Is SWR (the library) the same as stale-while-revalidate (the header)?",[14,1040,1041],{},"They share the strategy but operate at different layers. The header instructs HTTP caches (browser, CDN); the library manages an in-memory cache of application data. They complement each other: the library avoids requests, and when it does request, the header lets the CDN answer instantly.",[1031,1043,1045,1048],{"className":1044},[1034],[1036,1046,1047],{},"Which should I choose, SWR or TanStack Query?",[14,1049,1050],{},"SWR is minimal and focused on reading data; TanStack Query offers more control over mutations, invalidation, pagination and persistence. Both implement the same caching idea well; choose based on how much mutation and cache-management functionality you need.",[1031,1052,1054,1057],{"className":1053},[1034],[1036,1055,1056],{},"Does client caching help Core Web Vitals?",[14,1058,1059],{},"It helps route transitions and interaction responsiveness in SPAs (fewer loading states, fewer renders). It does not affect the first page load, where server rendering and HTTP caching matter more.",[1031,1061,1063,1066],{"className":1062},[1034],[1036,1064,1065],{},"How do I avoid stale data after a user's own change?",[14,1067,1068],{},"Invalidate or update the affected queries in the mutation's success handler, or use optimistic updates that write the expected result into the cache immediately.",[1031,1070,1072,1075],{"className":1071},[1034],[1036,1073,1074],{},"Should staleTime match the API's Cache-Control?",[14,1076,1077,1078,1080,1081,1084],{},"Roughly. A client ",[29,1079,246],{}," much shorter than the CDN's ",[29,1082,1083],{},"s-maxage"," produces refetches that the CDN answers from cache — cheap, but pointless for freshness. Aligning them avoids requests that cannot return new data.",[1031,1086,1088,1091],{"className":1087},[1034],[1036,1089,1090],{},"Does the query cache use memory on low-end devices?",[14,1092,1093,1094,1097],{},"Yes, proportionally to the data kept. ",[29,1095,1096],{},"gcTime"," controls how long unused entries persist; lower it for apps with large datasets, or avoid caching very large responses in memory.",[188,1099,1101],{"id":1100},"related","Related",[193,1103,1104,1111,1118],{},[196,1105,1106,1110],{},[18,1107,1109],{"href":1108},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fchoosing-swr-windows-for-html-and-apis\u002F","Choosing SWR windows for HTML and APIs"," — server-side staleness windows to pair with these settings.",[196,1112,1113,1117],{},[18,1114,1116],{"href":1115},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fcaching-api-responses-at-the-cdn\u002F","Caching API responses at the CDN"," — the edge layer under the client cache.",[196,1119,1120,1124],{},[18,1121,1123],{"href":1122},"\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Ftracking-route-transition-time-with-user-timing\u002F","Tracking route transition time with User Timing"," — measuring the effect.",[1126,1127,1129],"script",{"type":1128},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Stale-While-Revalidate Data Fetching with SWR and TanStack Query\",\n  \"description\": \"How client-side SWR data libraries make route transitions instant, and how to configure staleness, deduplication and prefetching without hammering the API.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Move data fetching into a query library\",\n      \"text\": \"Expected outcome: revisiting the view renders instantly from cache; refetches happen at most once a minute.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Configure global defaults deliberately\",\n      \"text\": \"Configure global defaults deliberately\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Show cached data during background refetch\",\n      \"text\": \"Render from data whenever it exists, and show a subtle indicator for isFetching rather than a full loading state.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Prefetch on intent\",\n      \"text\": \"Expected outcome: first visits to likely next views are also instant.\"\n    }\n  ]\n}\n",[1126,1131,1132],{"type":1128},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Stale-While-Revalidate Data Fetching with SWR and TanStack Query\",\n  \"description\": \"How client-side SWR data libraries make route transitions instant, and how to configure staleness, deduplication and prefetching without hammering the API.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query\u002F\"\n  }\n}\n",[1126,1134,1135],{"type":1128},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Advanced Caching Strategies & CDN Architecture\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Stale-While-Revalidate Implementation\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Stale-While-Revalidate Data Fetching with SWR and TanStack Query\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query\u002F\"\n    }\n  ]\n}\n",[1137,1138,1139],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}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);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":357,"searchDepth":384,"depth":384,"links":1141},[1142,1143,1144,1150,1151,1152,1153,1154,1155,1156],{"id":190,"depth":384,"text":191},{"id":227,"depth":384,"text":228},{"id":344,"depth":384,"text":345,"children":1145},[1146,1147,1148,1149],{"id":349,"depth":404,"text":350},{"id":593,"depth":404,"text":594},{"id":671,"depth":404,"text":672},{"id":684,"depth":404,"text":685},{"id":922,"depth":384,"text":923},{"id":929,"depth":384,"text":930},{"id":940,"depth":384,"text":941},{"id":962,"depth":384,"text":963},{"id":996,"depth":384,"text":997},{"id":1028,"depth":384,"text":1029},{"id":1100,"depth":384,"text":1101},"How client-side SWR data libraries make route transitions instant, and how to configure staleness, deduplication and prefetching without hammering the API.","md",{"slug":12,"type":1160,"breadcrumb":1161,"datePublished":1168,"dateModified":1168},"article",[1162,1164,1165,1166],{"name":1163,"url":948},"Home",{"name":26,"url":25},{"name":21,"url":20},{"name":5,"url":1167},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query",{"title":1172,"description":1173},"SWR and TanStack Query: Stale-While-Revalidate Data","Apply stale-while-revalidate in the client with SWR and TanStack Query: show cached data instantly, refresh in the background, tune staleTime, avoid refetch storms.","advanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-data-fetching-with-swr-and-tanstack-query\u002Findex","2rBB1CefcXvdSfud4BHj7YEuj0MPDry_FHOGXKtIEtk",[1177,1181],{"title":1178,"path":1179,"stem":1180,"children":-1},"Choosing SWR Windows for HTML and APIs","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fchoosing-swr-windows-for-html-and-apis","advanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fchoosing-swr-windows-for-html-and-apis\u002Findex",{"title":1182,"path":1183,"stem":1184,"children":-1},"Stale-While-Revalidate with nginx proxy_cache","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-with-nginx-proxy-cache","advanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fstale-while-revalidate-with-nginx-proxy-cache\u002Findex",1791308074951]