[{"data":1,"prerenderedAt":1047},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F":1038},{"id":4,"title":5,"body":6,"description":1018,"extension":1019,"meta":1020,"navigation":1031,"path":1032,"seo":1033,"stem":1036,"__hash__":1037},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002Findex.md","SWR vs cache-first Service Worker for React SPAs",{"type":7,"value":8,"toc":1010},"minimark",[9,13,33,46,51,58,69,184,188,308,312,320,338,341,358,361,365,368,670,673,864,877,881,884,948,956,960,995,1000,1003,1006],[10,11,5],"h1",{"id":12},"swr-vs-cache-first-service-worker-for-react-spas",[14,15,16,17,22,23,27,28,32],"p",{},"This comparison sits under the broader ",[18,19,21],"a",{"href":20},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F","Service Worker caching strategies"," guide within ",[18,24,26],{"href":25},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",", and isolates a single decision a React team faces once the worker is already intercepting ",[29,30,31],"code",{},"fetch",": should a given route be served stale-while-revalidate or cache-first?",[14,34,35,36,40,41,45],{},"The two strategies look superficially similar — both answer from the Cache API on a hit — but they diverge sharply on freshness, network behaviour, and how they perturb ",[18,37,39],{"href":38},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","Interaction to Next Paint"," and ",[18,42,44],{"href":43},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002F","Largest Contentful Paint",". Picking the wrong one per route is the most common cause of either visible staleness or wasted background bandwidth in a React SPA. The actionable boundaries are unchanged: keep LCP \u003C 2.5s, INP \u003C 200ms, and any work the worker schedules on the main thread under the 50ms long-task budget.",[47,48,50],"h2",{"id":49},"how-each-strategy-resolves-a-request","How each strategy resolves a request",[14,52,53,54,57],{},"Cache-first checks the Cache API first and returns the cached ",[29,55,56],{},"Response"," if present, touching the network only on a miss. Once a response is stored, the user never sees a newer version until you explicitly invalidate or version the cache key.",[14,59,60,61,65,66,68],{},"Stale-while-revalidate (SWR) also returns the cached response immediately, but it ",[62,63,64],"em",{},"additionally"," kicks off a background ",[29,67,31],{}," to the origin and writes the fresh response into the cache for next time. The current navigation is just as fast as cache-first; the difference is the extra request and the eventual freshness it buys.",[14,70,71],{},[72,73,80,81,80,85,80,89,80,99,80,106,80,111,80,119,80,122,80,128,80,132,80,138,80,143,80,147,80,150,80,152,80,159,80,162,80,164,80,169,80,173,80,176,80,179,80],"svg",{"xmlns":74,"viewBox":75,"width":76,"role":77,"ariaLabel":78,"style":79},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","Sequence comparison of cache-first versus stale-while-revalidate service worker request handling","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[82,83,84],"title",{},"Cache-first vs stale-while-revalidate flow",[86,87,88],"desc",{},"Both serve from cache instantly; SWR adds a background revalidation fetch that updates the cache.",[90,91],"rect",{"x":92,"y":92,"width":93,"height":94,"rx":95,"fill":96,"stroke":97,"style":98},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[100,101,105],"text",{"x":102,"y":103,"fill":97,"style":104},"24","36","font-size:16px;font-weight:700","Request handling per strategy",[100,107,110],{"x":102,"y":108,"fill":97,"style":109},"74","font-size:14px;font-weight:700","Cache-first",[90,112],{"x":102,"y":113,"width":114,"height":115,"rx":116,"fill":117,"stroke":117,"style":118},"90","150","50","6","#0466c8","fill-opacity:0.14",[90,120],{"x":121,"y":113,"width":114,"height":115,"rx":116,"fill":117,"stroke":117,"style":118},"214",[100,123,127],{"x":124,"y":125,"fill":97,"style":126},"99","120","font-size:12px;text-anchor:middle","Cache hit",[100,129,131],{"x":130,"y":125,"fill":97,"style":126},"289","Return cached",[133,134],"line",{"x1":135,"y1":136,"x2":121,"y2":136,"stroke":97,"style":137},"174","115","stroke-opacity:0.4",[100,139,142],{"x":140,"y":125,"fill":97,"style":141},"450","font-size:12px","No network on hit",[100,144,146],{"x":102,"y":145,"fill":97,"style":109},"190","Stale-while-revalidate",[90,148],{"x":102,"y":149,"width":114,"height":115,"rx":116,"fill":117,"stroke":117,"style":118},"206",[90,151],{"x":121,"y":149,"width":114,"height":115,"rx":116,"fill":117,"stroke":117,"style":118},[90,153],{"x":154,"y":149,"width":155,"height":115,"rx":116,"fill":156,"stroke":157,"style":158},"404","160","#ffc300","#b8860b","fill-opacity:0.22",[100,160,127],{"x":124,"y":161,"fill":97,"style":126},"236",[100,163,131],{"x":130,"y":161,"fill":97,"style":126},[100,165,168],{"x":166,"y":167,"fill":97,"style":126},"484","230","Background",[100,170,172],{"x":166,"y":171,"fill":97,"style":126},"248","refetch + store",[133,174],{"x1":135,"y1":175,"x2":121,"y2":175,"stroke":97,"style":137},"231",[133,177],{"x1":178,"y1":175,"x2":154,"y2":175,"stroke":97,"style":137},"364",[100,180,183],{"x":102,"y":181,"fill":97,"style":182},"300","font-size:13px","Same time-to-paint; SWR trades extra bandwidth for next-visit freshness.",[47,185,187],{"id":186},"decision-matrix","Decision matrix",[189,190,191,205],"table",{},[192,193,194],"thead",{},[195,196,197,201,203],"tr",{},[198,199,200],"th",{},"Dimension",[198,202,110],{},[198,204,146],{},[206,207,208,220,231,242,253,264,275,286,297],"tbody",{},[195,209,210,214,217],{},[211,212,213],"td",{},"First-visit freshness",[211,215,216],{},"Whatever was cached; can be arbitrarily old",[211,218,219],{},"Same stale answer, but refreshed for next visit",[195,221,222,225,228],{},[211,223,224],{},"Network requests per hit",[211,226,227],{},"Zero",[211,229,230],{},"One background request per hit",[195,232,233,236,239],{},[211,234,235],{},"Offline behaviour",[211,237,238],{},"Excellent — never needs network",[211,240,241],{},"Good — serves stale, background fetch fails silently",[195,243,244,247,250],{},[211,245,246],{},"INP impact",[211,248,249],{},"Lowest — no extra work after response",[211,251,252],{},"Slight — background fetch + cache write competes for I\u002FO",[195,254,255,258,261],{},[211,256,257],{},"LCP impact (hit)",[211,259,260],{},"Identical to SWR — both paint from cache",[211,262,263],{},"Identical to SWR on the hit path",[195,265,266,269,272],{},[211,267,268],{},"Staleness window",[211,270,271],{},"Until explicit invalidation\u002Fversioning",[211,273,274],{},"One visit (next load is fresh)",[195,276,277,280,283],{},[211,278,279],{},"Bandwidth cost",[211,281,282],{},"Minimal",[211,284,285],{},"Higher — refetches even when unchanged",[195,287,288,291,294],{},[211,289,290],{},"Implementation complexity",[211,292,293],{},"Low",[211,295,296],{},"Moderate (must guard the background fetch)",[195,298,299,302,305],{},[211,300,301],{},"Best for",[211,303,304],{},"Hashed JS\u002FCSS, fonts, immutable media",[211,306,307],{},"App shell HTML, avatars, config JSON, feed thumbnails",[47,309,311],{"id":310},"when-to-pick-which","When to pick which",[14,313,314,315,319],{},"Pick ",[316,317,318],"strong",{},"cache-first"," for any resource whose URL already encodes its content — content-hashed bundles, fonts, immutable images. A new deploy produces a new URL, so there is no staleness to revalidate against, and the background request SWR would issue is pure waste. This is also the right default for offline-first React shells where predictability matters more than freshness.",[14,321,314,322,325,326,329,330,333,334,337],{},[316,323,324],{},"SWR"," for resources that share a stable URL but whose body changes — the unhashed ",[29,327,328],{},"index.html"," app shell, a user avatar at ",[29,331,332],{},"\u002Fme\u002Favatar.png",", a ",[29,335,336],{},"\u002Fconfig.json",", or non-critical list thumbnails. You accept one revision of staleness in exchange for the cache silently healing itself without an explicit purge.",[14,339,340],{},"For genuinely freshness-critical data (cart totals, auth state, prices) neither pattern is correct — use network-first with a cache fallback, because showing a stale price even once is unacceptable.",[14,342,343,344,346,347,349,350,353,354,357],{},"The subtle trap specific to React SPAs is that the build emits a small number of long-lived, hashed bundles plus one short-lived ",[29,345,328],{}," that references them by hash. Teams reflexively apply a single strategy to everything the worker intercepts and then wonder why a deploy either fails to roll out (cache-first on ",[29,348,328],{}," pins users to a stale entry point that loads now-purged bundles) or feels wasteful (SWR on ",[29,351,352],{},"app.8f3a9c.js"," refetches bytes that are immutable by construction). The correct mental model is per-resource, not per-app: hash-versioned URLs are cache-first because their content can never change under a fixed URL, and the single mutable entry document is SWR so a new deploy is picked up within one navigation. Getting this split right is what makes a worker-backed React app deploy cleanly ",[62,355,356],{},"and"," feel instant.",[14,359,360],{},"A second React-specific consideration is hydration timing. Both strategies paint the cached shell at the same speed, but SWR's background fetch lands while React is hydrating and attaching event listeners. If that fetch resolves into a state update mid-hydration, you can trigger an extra render pass right as the user is trying to interact — exactly the window that shows up as elevated INP. Cache-first sidesteps this entirely because nothing arrives after the initial paint. When you do choose SWR for a route that feeds React state, gate the state update behind a check that the data actually changed, so an unchanged background response does not force a re-render for nothing.",[47,362,364],{"id":363},"reference-implementations","Reference implementations",[14,366,367],{},"A hand-rolled cache-first handler, scoped to hashed assets:",[369,370,375],"pre",{"className":371,"code":372,"language":373,"meta":374,"style":374},"language-javascript shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F Cache-first: answer from cache, hit network only on a miss.\nself.addEventListener('fetch', (event) => {\n  const url = new URL(event.request.url);\n  if (!\u002F\\.[0-9a-f]{8,}\\.(js|css|woff2)$\u002F.test(url.pathname)) return;\n  event.respondWith(\n    caches.match(event.request).then((cached) =>\n      cached || fetch(event.request).then((res) => {\n        const copy = res.clone();\n        caches.open('assets-v1').then((c) => c.put(event.request, copy));\n        return res;\n      })\n    )\n  );\n  \u002F\u002F trade-off: never revalidates — only safe for content-hashed URLs.\n  \u002F\u002F Apply this to index.html and users will be stuck on an old build forever.\n});\n","javascript","",[29,376,377,385,420,442,503,515,541,568,588,625,634,640,646,652,658,664],{"__ignoreMap":374},[378,379,381],"span",{"class":133,"line":380},1,[378,382,384],{"class":383},"sIIH1","\u002F\u002F Cache-first: answer from cache, hit network only on a miss.\n",[378,386,388,392,396,399,403,406,410,413,417],{"class":133,"line":387},2,[378,389,391],{"class":390},"syybb","self.",[378,393,395],{"class":394},"ssM3C","addEventListener",[378,397,398],{"class":390},"(",[378,400,402],{"class":401},"s-_DF","'fetch'",[378,404,405],{"class":390},", (",[378,407,409],{"class":408},"seIZK","event",[378,411,412],{"class":390},") ",[378,414,416],{"class":415},"sP5qI","=>",[378,418,419],{"class":390}," {\n",[378,421,423,426,430,433,436,439],{"class":133,"line":422},3,[378,424,425],{"class":415},"  const",[378,427,429],{"class":428},"sf6mN"," url",[378,431,432],{"class":415}," =",[378,434,435],{"class":415}," new",[378,437,438],{"class":394}," URL",[378,440,441],{"class":390},"(event.request.url);\n",[378,443,445,448,451,454,457,461,464,467,469,472,475,478,480,483,486,488,491,494,497,500],{"class":133,"line":444},4,[378,446,447],{"class":415},"  if",[378,449,450],{"class":390}," (",[378,452,453],{"class":415},"!",[378,455,456],{"class":401},"\u002F",[378,458,460],{"class":459},"s6uau","\\.",[378,462,463],{"class":428},"[0-9a-f]",[378,465,466],{"class":415},"{8,}",[378,468,460],{"class":459},[378,470,471],{"class":401},"(js",[378,473,474],{"class":415},"|",[378,476,477],{"class":401},"css",[378,479,474],{"class":415},[378,481,482],{"class":401},"woff2)",[378,484,485],{"class":415},"$",[378,487,456],{"class":401},[378,489,490],{"class":390},".",[378,492,493],{"class":394},"test",[378,495,496],{"class":390},"(url.pathname)) ",[378,498,499],{"class":415},"return",[378,501,502],{"class":390},";\n",[378,504,506,509,512],{"class":133,"line":505},5,[378,507,508],{"class":390},"  event.",[378,510,511],{"class":394},"respondWith",[378,513,514],{"class":390},"(\n",[378,516,518,521,524,527,530,533,536,538],{"class":133,"line":517},6,[378,519,520],{"class":390},"    caches.",[378,522,523],{"class":394},"match",[378,525,526],{"class":390},"(event.request).",[378,528,529],{"class":394},"then",[378,531,532],{"class":390},"((",[378,534,535],{"class":408},"cached",[378,537,412],{"class":390},[378,539,540],{"class":415},"=>\n",[378,542,544,547,550,553,555,557,559,562,564,566],{"class":133,"line":543},7,[378,545,546],{"class":390},"      cached ",[378,548,549],{"class":415},"||",[378,551,552],{"class":394}," fetch",[378,554,526],{"class":390},[378,556,529],{"class":394},[378,558,532],{"class":390},[378,560,561],{"class":408},"res",[378,563,412],{"class":390},[378,565,416],{"class":415},[378,567,419],{"class":390},[378,569,571,574,577,579,582,585],{"class":133,"line":570},8,[378,572,573],{"class":415},"        const",[378,575,576],{"class":428}," copy",[378,578,432],{"class":415},[378,580,581],{"class":390}," res.",[378,583,584],{"class":394},"clone",[378,586,587],{"class":390},"();\n",[378,589,591,594,597,599,602,605,607,609,612,614,616,619,622],{"class":133,"line":590},9,[378,592,593],{"class":390},"        caches.",[378,595,596],{"class":394},"open",[378,598,398],{"class":390},[378,600,601],{"class":401},"'assets-v1'",[378,603,604],{"class":390},").",[378,606,529],{"class":394},[378,608,532],{"class":390},[378,610,611],{"class":408},"c",[378,613,412],{"class":390},[378,615,416],{"class":415},[378,617,618],{"class":390}," c.",[378,620,621],{"class":394},"put",[378,623,624],{"class":390},"(event.request, copy));\n",[378,626,628,631],{"class":133,"line":627},10,[378,629,630],{"class":415},"        return",[378,632,633],{"class":390}," res;\n",[378,635,637],{"class":133,"line":636},11,[378,638,639],{"class":390},"      })\n",[378,641,643],{"class":133,"line":642},12,[378,644,645],{"class":390},"    )\n",[378,647,649],{"class":133,"line":648},13,[378,650,651],{"class":390},"  );\n",[378,653,655],{"class":133,"line":654},14,[378,656,657],{"class":383},"  \u002F\u002F trade-off: never revalidates — only safe for content-hashed URLs.\n",[378,659,661],{"class":133,"line":660},15,[378,662,663],{"class":383},"  \u002F\u002F Apply this to index.html and users will be stuck on an old build forever.\n",[378,665,667],{"class":133,"line":666},16,[378,668,669],{"class":390},"});\n",[14,671,672],{},"A stale-while-revalidate handler for the mutable app shell:",[369,674,676],{"className":371,"code":675,"language":373,"meta":374,"style":374},"\u002F\u002F SWR: serve cached immediately, refresh in the background for next time.\nasync function staleWhileRevalidate(request, cacheName = 'shell-v1') {\n  const cache = await caches.open(cacheName);\n  const cached = await cache.match(request);\n  const network = fetch(request)\n    .then((res) => {\n      if (res.ok) cache.put(request, res.clone());\n      return res;\n    })\n    .catch(() => cached); \u002F\u002F offline: fall back to whatever we had\n  return cached || network;\n  \u002F\u002F trade-off: issues a network request on every hit. Do NOT use for\n  \u002F\u002F immutable hashed assets — it burns bandwidth refetching bytes that\n  \u002F\u002F cannot have changed, and offers zero freshness benefit in return.\n}\n",[29,677,678,683,713,733,752,766,783,801,808,813,831,844,849,854,859],{"__ignoreMap":374},[378,679,680],{"class":133,"line":380},[378,681,682],{"class":383},"\u002F\u002F SWR: serve cached immediately, refresh in the background for next time.\n",[378,684,685,688,691,694,696,699,702,705,707,710],{"class":133,"line":387},[378,686,687],{"class":415},"async",[378,689,690],{"class":415}," function",[378,692,693],{"class":394}," staleWhileRevalidate",[378,695,398],{"class":390},[378,697,698],{"class":408},"request",[378,700,701],{"class":390},", ",[378,703,704],{"class":408},"cacheName",[378,706,432],{"class":415},[378,708,709],{"class":401}," 'shell-v1'",[378,711,712],{"class":390},") {\n",[378,714,715,717,720,722,725,728,730],{"class":133,"line":422},[378,716,425],{"class":415},[378,718,719],{"class":428}," cache",[378,721,432],{"class":415},[378,723,724],{"class":415}," await",[378,726,727],{"class":390}," caches.",[378,729,596],{"class":394},[378,731,732],{"class":390},"(cacheName);\n",[378,734,735,737,740,742,744,747,749],{"class":133,"line":444},[378,736,425],{"class":415},[378,738,739],{"class":428}," cached",[378,741,432],{"class":415},[378,743,724],{"class":415},[378,745,746],{"class":390}," cache.",[378,748,523],{"class":394},[378,750,751],{"class":390},"(request);\n",[378,753,754,756,759,761,763],{"class":133,"line":505},[378,755,425],{"class":415},[378,757,758],{"class":428}," network",[378,760,432],{"class":415},[378,762,552],{"class":394},[378,764,765],{"class":390},"(request)\n",[378,767,768,771,773,775,777,779,781],{"class":133,"line":517},[378,769,770],{"class":390},"    .",[378,772,529],{"class":394},[378,774,532],{"class":390},[378,776,561],{"class":408},[378,778,412],{"class":390},[378,780,416],{"class":415},[378,782,419],{"class":390},[378,784,785,788,791,793,796,798],{"class":133,"line":543},[378,786,787],{"class":415},"      if",[378,789,790],{"class":390}," (res.ok) cache.",[378,792,621],{"class":394},[378,794,795],{"class":390},"(request, res.",[378,797,584],{"class":394},[378,799,800],{"class":390},"());\n",[378,802,803,806],{"class":133,"line":570},[378,804,805],{"class":415},"      return",[378,807,633],{"class":390},[378,809,810],{"class":133,"line":590},[378,811,812],{"class":390},"    })\n",[378,814,815,817,820,823,825,828],{"class":133,"line":627},[378,816,770],{"class":390},[378,818,819],{"class":394},"catch",[378,821,822],{"class":390},"(() ",[378,824,416],{"class":415},[378,826,827],{"class":390}," cached); ",[378,829,830],{"class":383},"\u002F\u002F offline: fall back to whatever we had\n",[378,832,833,836,839,841],{"class":133,"line":636},[378,834,835],{"class":415},"  return",[378,837,838],{"class":390}," cached ",[378,840,549],{"class":415},[378,842,843],{"class":390}," network;\n",[378,845,846],{"class":133,"line":642},[378,847,848],{"class":383},"  \u002F\u002F trade-off: issues a network request on every hit. Do NOT use for\n",[378,850,851],{"class":133,"line":648},[378,852,853],{"class":383},"  \u002F\u002F immutable hashed assets — it burns bandwidth refetching bytes that\n",[378,855,856],{"class":133,"line":654},[378,857,858],{"class":383},"  \u002F\u002F cannot have changed, and offers zero freshness benefit in return.\n",[378,860,861],{"class":133,"line":660},[378,862,863],{"class":390},"}\n",[14,865,866,867,40,870,873,874,490],{},"If you are on Workbox rather than a hand-rolled worker, the same decision maps directly onto ",[29,868,869],{},"CacheFirst",[29,871,872],{},"StaleWhileRevalidate"," route handlers; the matrix above still governs which to register per ",[29,875,876],{},"registerRoute",[47,878,880],{"id":879},"verification","Verification",[14,882,883],{},"Confirm each route resolves with the strategy you intended before shipping:",[885,886,887,895,923,934,937],"ol",{},[888,889,890,891,894],"li",{},"Open DevTools, Application, Service Workers, and tick ",[316,892,893],{},"Update on reload",". Reload twice.",[888,896,897,898,901,902,905,906,909,910,912,913,916,917],{},"In the Network tab, set the ",[316,899,900],{},"Size"," column to visible. A cache-first hit shows ",[29,903,904],{},"(ServiceWorker)"," with ",[316,907,908],{},"no"," matching origin request. An SWR hit shows the ",[29,911,904],{}," response ",[316,914,915],{},"plus"," a background request to the same URL.\n",[918,919,920],"ul",{},[888,921,922],{},"Expected outcome: hashed assets show zero background requests; the app shell shows exactly one.",[888,924,925,926,929,930,933],{},"Throttle to ",[316,927,928],{},"Offline"," and reload. Both strategies must still paint from cache — if SWR fails, your background-fetch ",[29,931,932],{},".catch"," is missing.",[888,935,936],{},"In the Performance panel, record a reload and confirm no service-worker callback creates a task over the 50ms long-task budget; an oversized synchronous cache write under SWR is the usual culprit.",[888,938,939,940,943,944,947],{},"In RUM, watch INP p75 for SWR routes after rollout — a regression above 200ms means the background ",[29,941,942],{},"cache.put"," is contending with interaction work; defer it with ",[29,945,946],{},"event.waitUntil"," so it runs off the critical path.",[14,949,950,951,955],{},"Choosing per route, rather than globally, is what keeps a React SPA both fast and fresh. For the deeper invalidation question that cache-first eventually forces, see the ",[18,952,954],{"href":953},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002F","cache invalidation patterns"," guide.",[47,957,959],{"id":958},"related","Related",[918,961,962,970,977,984,990],{},[888,963,964,966,967,969],{},[18,965,21],{"href":20}," — the parent guide on ",[29,968,31],{}," interception and strategy selection.",[888,971,972,976],{},[18,973,975],{"href":974},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production\u002F","Debugging Service Worker cache misses in production"," — when neither strategy is hitting the cache at all.",[888,978,979,983],{},[18,980,982],{"href":981},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fswr-cache-control-vs-service-worker-revalidation\u002F","SWR via Cache-Control vs Service Worker revalidation"," — the same revalidation idea, but at the edge.",[888,985,986,989],{},[18,987,988],{"href":953},"Cache invalidation patterns"," — how cache-first resources eventually get refreshed.",[888,991,992,994],{},[18,993,26],{"href":25}," — the full caching architecture this fits into.",[996,997,999],"script",{"type":998},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"Article\",\n  \"headline\": \"SWR vs cache-first Service Worker for React SPAs\",\n  \"description\": \"A decision matrix comparing stale-while-revalidate and cache-first Service Worker strategies for React single-page applications, covering freshness, offline, INP\u002FLCP impact, and complexity.\",\n  \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-06-18\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\", \"logo\": { \"@type\": \"ImageObject\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Flogo.svg\" } },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F\" }\n}\n",[996,1001,1002],{"type":998},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Choose between cache-first and stale-while-revalidate per route in a React SPA\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"name\": \"Update on reload\", \"text\": \"Enable Update on reload in DevTools and reload twice to settle the worker.\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Inspect network\", \"text\": \"Verify hashed assets show no background request and the app shell shows one.\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Test offline\", \"text\": \"Throttle to Offline and confirm both strategies still paint from cache.\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Check tasks\", \"text\": \"Record a reload and confirm no worker callback exceeds the 50ms long-task budget.\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Watch RUM INP\", \"text\": \"Defer background cache writes with waitUntil if INP p75 regresses past 200ms.\" }\n  ]\n}\n",[996,1004,1005],{"type":998},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Advanced Caching Strategies & CDN Architecture\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Service Worker Caching Strategies\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"SWR vs cache-first Service Worker for React SPAs\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F\" }\n  ]\n}\n",[1007,1008,1009],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .s6uau, html code.shiki .s6uau{--shiki-default:#024C1A;--shiki-default-font-weight:bold;--shiki-dark:#024C1A;--shiki-dark-font-weight:bold;--shiki-light:#024C1A;--shiki-light-font-weight:bold}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":374,"searchDepth":387,"depth":387,"links":1011},[1012,1013,1014,1015,1016,1017],{"id":49,"depth":387,"text":50},{"id":186,"depth":387,"text":187},{"id":310,"depth":387,"text":311},{"id":363,"depth":387,"text":364},{"id":879,"depth":387,"text":880},{"id":958,"depth":387,"text":959},"A decision matrix comparing stale-while-revalidate and cache-first Service Worker strategies for React single-page applications.","md",{"slug":12,"type":1021,"breadcrumb":1022,"datePublished":1030,"dateModified":1030},"long_tail",[1023,1025,1026,1028],{"name":1024,"url":456},"Home",{"name":26,"url":25},{"name":1027,"url":20},"Service Worker Caching Strategies",{"name":5,"url":1029},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F","2026-06-18",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas",{"title":1034,"description":1035},"SWR vs Cache-First Service Worker (React SPAs)","Compare stale-while-revalidate and cache-first Service Worker strategies for React SPAs with a decision matrix covering freshness, offline, INP\u002FLCP impact, and complexity.","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002Findex","kWo_HeyhyjkYqI24rqeTGroho0oSPFSDmGNZdqTEVEs",[1039,1043],{"title":1040,"path":1041,"stem":1042,"children":-1},"Debugging Service Worker Cache Misses in Production","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production\u002Findex",{"title":1044,"path":1045,"stem":1046,"children":-1},"Stale-While-Revalidate Implementation","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation","advanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Findex",1782237170939]