[{"data":1,"prerenderedAt":1721},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas":1712},{"id":4,"title":5,"body":6,"description":1691,"extension":1692,"meta":1693,"navigation":1705,"path":1706,"seo":1707,"stem":1710,"__hash__":1711},"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":1671},"minimark",[9,13,33,46,51,58,69,184,188,308,457,461,464,549,552,556,561,570,574,583,587,590,594,597,600,604,607,611,614,850,857,861,864,1012,1015,1019,1031,1207,1210,1214,1217,1335,1338,1342,1345,1472,1475,1497,1501,1504,1549,1605,1616,1620,1656,1661,1664,1667],[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, part of the wider work on ",[18,24,26],{"href":25},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","advanced caching and CDN architecture",". It 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",[14,309,310,386,390,391,80,396],{},[72,311,80,314,80,317,80,320,80,323,80,327,80,332,80,338,80,343,80,348,80,353,80,357,80,363,80,368,80,371,80,377,80,381],{"xmlns":74,"viewBox":312,"width":76,"role":77,"ariaLabel":313,"style":79},"0 0 760 404","Decision tree routing React SPA resource types to cache-first, network-first, or stale-while-revalidate",[82,315,316],{},"Per-resource strategy decision tree",[86,318,319],{},"Two questions route each request: a content-hashed or immutable URL goes cache-first; a resource whose stale value is a correctness bug goes network-first; everything else goes stale-while-revalidate.",[90,321],{"x":92,"y":92,"width":93,"height":322,"rx":95,"fill":96,"stroke":97,"style":98},"402",[100,324,326],{"x":102,"y":325,"fill":97,"style":104},"34","Which caching strategy for each resource?",[90,328],{"x":115,"y":329,"width":181,"height":330,"rx":116,"fill":97,"stroke":97,"style":331},"56","42","fill-opacity:0.06;stroke-opacity:0.35",[100,333,337],{"x":334,"y":335,"fill":97,"style":336},"200","82","font-size:13px;font-weight:700;text-anchor:middle","Incoming route request",[133,339],{"x1":334,"y1":340,"x2":334,"y2":341,"stroke":97,"style":342},"98","116","stroke-opacity:0.5",[344,345],"polygon",{"points":346,"fill":97,"style":347},"200,118 196,110 204,110","fill-opacity:0.6",[90,349],{"x":115,"y":350,"width":181,"height":351,"rx":116,"fill":97,"stroke":97,"style":352},"118","48","fill-opacity:0.04;stroke-opacity:0.45",[100,354,356],{"x":334,"y":355,"fill":97,"style":126},"147","Q1 · URL content-hashed \u002F immutable?",[100,358,362],{"x":359,"y":360,"fill":97,"style":361},"392","134","font-size:11px;font-weight:700","yes",[133,364],{"x1":365,"y1":366,"x2":367,"y2":366,"stroke":97,"style":342},"350","142","438",[344,369],{"points":370,"fill":97,"style":347},"446,142 438,138 438,146",[90,372],{"x":373,"y":374,"width":375,"height":376,"rx":116,"fill":117,"stroke":117,"style":118},"446","107","284","70",[100,378,110],{"x":379,"y":380,"fill":97,"style":336},"588","138",[100,382,385],{"x":379,"y":383,"fill":97,"style":384},"158","font-size:11px;text-anchor:middle","app.",[387,388,389],"span",{},"hash",".js · fonts · immutable media ",[100,392,395],{"x":393,"y":394,"fill":97,"style":361},"210","186","no",[133,397,80,399],{"x1":334,"y1":398,"x2":334,"y2":334,"stroke":97,"style":342},"166",[344,400,80,402],{"points":401,"fill":97,"style":347},"200,202 196,194 204,194",[90,403,80,407,80,410,80,413],{"x":115,"y":404,"width":405,"height":406,"rx":116,"fill":97,"stroke":97,"style":352},"202",300,48,[100,408,409],{"x":334,"y":175,"fill":97,"style":126},"Q2 · Stale value is a correctness bug?",[100,411,362],{"x":359,"y":412,"fill":97,"style":361},"218",[133,414,80,416],{"x1":365,"y1":415,"x2":367,"y2":415,"stroke":97,"style":342},"226",[344,417,80,419],{"points":418,"fill":97,"style":347},"446,226 438,222 438,230",[90,420,80,426,80,430,80,434,80,437],{"x":373,"y":421,"width":422,"height":423,"rx":116,"fill":424,"stroke":424,"style":425},"191",284,70,"#0353a4","fill-opacity:0.16",[100,427,429],{"x":379,"y":428,"fill":97,"style":336},"222","Network-first",[100,431,433],{"x":379,"y":432,"fill":97,"style":384},"242","cart totals · auth state · prices",[100,435,395],{"x":393,"y":436,"fill":97,"style":361},"270",[133,438,80,440],{"x1":334,"y1":439,"x2":334,"y2":375,"stroke":97,"style":342},"250",[344,441,80,443],{"points":442,"fill":97,"style":347},"200,286 196,278 204,278",[90,444,80,446,80,449,80,453,80],{"x":115,"y":445,"width":405,"height":423,"rx":116,"fill":156,"stroke":157,"style":158},"286",[100,447,146],{"x":334,"y":448,"fill":97,"style":336},"317",[100,450,452],{"x":334,"y":451,"fill":97,"style":384},"337","index.html shell · config.json · avatars",[100,454,456],{"x":102,"y":455,"fill":97,"style":141},"384","Route per resource, not per app: match each URL's mutability to a strategy.",[47,458,460],{"id":459},"rapid-diagnosis-which-strategy-is-each-route-actually-using","Rapid diagnosis: which strategy is each route actually using?",[14,462,463],{},"Before changing any handler, confirm what the worker does today per route. A wrong strategy is invisible until a deploy fails to roll out or a RUM regression appears, so read the live behaviour first.",[465,466,467,483,507,529,539],"ol",{},[468,469,470,474,475,478,479,482],"li",{},[471,472,473],"strong",{},"Application > Service Workers."," Tick ",[471,476,477],{},"Update on reload"," and reload twice so a fresh worker is in control and ",[29,480,481],{},"navigator.serviceWorker.controller"," is non-null.",[468,484,485,488,489,492,493,496,497,499,500,502,503,506],{},[471,486,487],{},"Network tab, Initiator column."," Apply the ",[29,490,491],{},"sw"," filter. A cache-first hit shows the ",[29,494,495],{},"(ServiceWorker)"," response with ",[471,498,395],{}," matching origin request; an SWR hit shows the ",[29,501,495],{}," response ",[471,504,505],{},"plus"," a second background request to the same URL.",[468,508,509,512,513,516,517,520,521,524,525,528],{},[471,510,511],{},"Cross-check against the URL shape."," Any request whose path matches ",[29,514,515],{},"*.[hash].js|css|woff2"," that also fires a background request is mis-routed to SWR. Any request to ",[29,518,519],{},"index.html",", ",[29,522,523],{},"\u002Fconfig.json",", or an avatar that shows ",[62,526,527],{},"zero"," background request is mis-routed to cache-first.",[468,530,531,534,535,538],{},[471,532,533],{},"Application > Cache Storage."," Confirm hashed assets and the mutable shell live in ",[62,536,537],{},"separate, versioned"," cache buckets — a single shared bucket is the tell that one strategy is applied blindly to everything.",[468,540,541,544,545,548],{},[471,542,543],{},"Performance panel."," Record a reload and look for a service-worker ",[29,546,547],{},"cache.put"," callback landing during React hydration; a task over the 50ms long-task budget here is the signature of an SWR write contending with interaction work.",[14,550,551],{},"The goal of this pass is not merely to confirm a problem exists but to assign each route to one of the failure modes below before you touch a handler.",[47,553,555],{"id":554},"root-cause-analysis","Root cause analysis",[557,558,560],"h3",{"id":559},"_1-cache-first-on-the-mutable-entry-document","1. Cache-first on the mutable entry document",[14,562,563,564,566,567,569],{},"A React build emits a small number of long-lived, hashed bundles plus one short-lived ",[29,565,519],{}," that references them by hash. Serving ",[29,568,519],{}," cache-first pins users to a stale entry point that keeps requesting now-purged bundle hashes, so a deploy never reaches them and asset requests start 404-ing.",[557,571,573],{"id":572},"_2-swr-on-content-hashed-assets","2. SWR on content-hashed assets",[14,575,576,579,580,582],{},[29,577,578],{},"app.8f3a9c.js"," can never change under its URL — a new build mints a new hash. Applying SWR to it issues a background refetch on every hit that returns byte-identical content, burning bandwidth and a ",[29,581,547],{}," for zero freshness benefit.",[557,584,586],{"id":585},"_3-swr-revalidation-landing-mid-hydration","3. SWR revalidation landing mid-hydration",[14,588,589],{},"Both strategies paint the cached shell at the same speed, but SWR's background fetch resolves while React is hydrating and attaching listeners. If that response feeds a state update, it forces an extra render pass in exactly the window the user is trying to interact — the classic elevated-INP signature.",[557,591,593],{"id":592},"_4-either-strategy-on-freshness-critical-data","4. Either strategy on freshness-critical data",[14,595,596],{},"Cart totals, auth state, and prices must never render one revision stale. Cache-first shows an arbitrarily old value; SWR shows a one-revision-old value on first paint. For this class of data both are correctness bugs, not cosmetic delays — the fix is a different strategy entirely.",[14,598,599],{},"These are not equally likely. Failure mode 1 is the highest-severity because it silently breaks deploys; mode 2 is the most common waste; mode 3 is the subtlest and only shows up in field INP; mode 4 is a design error that a matrix lookup prevents. Diagnose the signature before writing a fix so you do not, for example, rescope a whole app to SWR to solve what was really a single mis-routed document.",[47,601,603],{"id":602},"step-by-step-resolution","Step-by-step resolution",[14,605,606],{},"Apply these in order — the first fix resolves the highest-severity failure mode and makes the rest mechanical.",[557,608,610],{"id":609},"fix-1-route-per-resource-not-per-application-highest-impact","Fix 1 — Route per resource, not per application (highest impact)",[14,612,613],{},"The correct mental model is per-resource: 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. Split the routing at the top of the fetch handler.",[615,616,621],"pre",{"className":617,"code":618,"language":619,"meta":620,"style":620},"language-javascript shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","self.addEventListener('fetch', (event) => {\n  const url = new URL(event.request.url);\n  const isHashedAsset = \u002F\\.[0-9a-f]{8,}\\.(js|css|woff2)$\u002F.test(url.pathname);\n  if (isHashedAsset) {\n    event.respondWith(cacheFirst(event.request));      \u002F\u002F immutable → cache-first\n  } else if (url.pathname === '\u002F' || url.pathname.endsWith('.html')) {\n    event.respondWith(staleWhileRevalidate(event.request)); \u002F\u002F shell → SWR\n  }\n  \u002F\u002F trade-off: anything not matched here falls through to the network. That is\n  \u002F\u002F deliberate — do NOT add a catch-all branch, or you will cache API responses\n  \u002F\u002F that belong under network-first (see Fix 5).\n});\n","javascript","",[29,622,623,658,680,734,743,764,802,820,826,832,838,844],{"__ignoreMap":620},[387,624,626,630,634,637,641,644,648,651,655],{"class":133,"line":625},1,[387,627,629],{"class":628},"syybb","self.",[387,631,633],{"class":632},"ssM3C","addEventListener",[387,635,636],{"class":628},"(",[387,638,640],{"class":639},"s-_DF","'fetch'",[387,642,643],{"class":628},", (",[387,645,647],{"class":646},"seIZK","event",[387,649,650],{"class":628},") ",[387,652,654],{"class":653},"sP5qI","=>",[387,656,657],{"class":628}," {\n",[387,659,661,664,668,671,674,677],{"class":133,"line":660},2,[387,662,663],{"class":653},"  const",[387,665,667],{"class":666},"sf6mN"," url",[387,669,670],{"class":653}," =",[387,672,673],{"class":653}," new",[387,675,676],{"class":632}," URL",[387,678,679],{"class":628},"(event.request.url);\n",[387,681,683,685,688,690,693,697,700,703,705,708,711,714,716,719,722,725,728,731],{"class":133,"line":682},3,[387,684,663],{"class":653},[387,686,687],{"class":666}," isHashedAsset",[387,689,670],{"class":653},[387,691,692],{"class":639}," \u002F",[387,694,696],{"class":695},"s6uau","\\.",[387,698,699],{"class":666},"[0-9a-f]",[387,701,702],{"class":653},"{8,}",[387,704,696],{"class":695},[387,706,707],{"class":639},"(js",[387,709,710],{"class":653},"|",[387,712,713],{"class":639},"css",[387,715,710],{"class":653},[387,717,718],{"class":639},"woff2)",[387,720,721],{"class":653},"$",[387,723,724],{"class":639},"\u002F",[387,726,727],{"class":628},".",[387,729,730],{"class":632},"test",[387,732,733],{"class":628},"(url.pathname);\n",[387,735,737,740],{"class":133,"line":736},4,[387,738,739],{"class":653},"  if",[387,741,742],{"class":628}," (isHashedAsset) {\n",[387,744,746,749,752,754,757,760],{"class":133,"line":745},5,[387,747,748],{"class":628},"    event.",[387,750,751],{"class":632},"respondWith",[387,753,636],{"class":628},[387,755,756],{"class":632},"cacheFirst",[387,758,759],{"class":628},"(event.request));      ",[387,761,763],{"class":762},"sIIH1","\u002F\u002F immutable → cache-first\n",[387,765,767,770,773,776,779,782,785,788,791,794,796,799],{"class":133,"line":766},6,[387,768,769],{"class":628},"  } ",[387,771,772],{"class":653},"else",[387,774,775],{"class":653}," if",[387,777,778],{"class":628}," (url.pathname ",[387,780,781],{"class":653},"===",[387,783,784],{"class":639}," '\u002F'",[387,786,787],{"class":653}," ||",[387,789,790],{"class":628}," url.pathname.",[387,792,793],{"class":632},"endsWith",[387,795,636],{"class":628},[387,797,798],{"class":639},"'.html'",[387,800,801],{"class":628},")) {\n",[387,803,805,807,809,811,814,817],{"class":133,"line":804},7,[387,806,748],{"class":628},[387,808,751],{"class":632},[387,810,636],{"class":628},[387,812,813],{"class":632},"staleWhileRevalidate",[387,815,816],{"class":628},"(event.request)); ",[387,818,819],{"class":762},"\u002F\u002F shell → SWR\n",[387,821,823],{"class":133,"line":822},8,[387,824,825],{"class":628},"  }\n",[387,827,829],{"class":133,"line":828},9,[387,830,831],{"class":762},"  \u002F\u002F trade-off: anything not matched here falls through to the network. That is\n",[387,833,835],{"class":133,"line":834},10,[387,836,837],{"class":762},"  \u002F\u002F deliberate — do NOT add a catch-all branch, or you will cache API responses\n",[387,839,841],{"class":133,"line":840},11,[387,842,843],{"class":762},"  \u002F\u002F that belong under network-first (see Fix 5).\n",[387,845,847],{"class":133,"line":846},12,[387,848,849],{"class":628},"});\n",[14,851,852,853,856],{},"Expected outcome: a new deploy is picked up within one navigation while hashed bundles stop refetching — deploys roll out cleanly ",[62,854,855],{},"and"," the app still paints instantly.",[557,858,860],{"id":859},"fix-2-cache-first-for-content-hashed-assets","Fix 2 — Cache-first for content-hashed assets",[14,862,863],{},"Scope a hand-rolled cache-first handler to hashed bundles, fonts, and immutable media.",[615,865,867],{"className":617,"code":866,"language":619,"meta":620,"style":620},"\u002F\u002F Cache-first: answer from cache, hit network only on a miss.\nasync function cacheFirst(request, cacheName = 'assets-v1') {\n  const cached = await caches.match(request);\n  if (cached) return cached;\n  const res = await fetch(request);\n  const cache = await caches.open(cacheName);\n  cache.put(request, res.clone());\n  return res;\n  \u002F\u002F trade-off: never revalidates — only safe for content-hashed URLs. Apply\n  \u002F\u002F this to index.html and users will be stuck on an old build forever.\n}\n",[29,868,869,874,903,924,937,953,972,989,997,1002,1007],{"__ignoreMap":620},[387,870,871],{"class":133,"line":625},[387,872,873],{"class":762},"\u002F\u002F Cache-first: answer from cache, hit network only on a miss.\n",[387,875,876,879,882,885,887,890,892,895,897,900],{"class":133,"line":660},[387,877,878],{"class":653},"async",[387,880,881],{"class":653}," function",[387,883,884],{"class":632}," cacheFirst",[387,886,636],{"class":628},[387,888,889],{"class":646},"request",[387,891,520],{"class":628},[387,893,894],{"class":646},"cacheName",[387,896,670],{"class":653},[387,898,899],{"class":639}," 'assets-v1'",[387,901,902],{"class":628},") {\n",[387,904,905,907,910,912,915,918,921],{"class":133,"line":682},[387,906,663],{"class":653},[387,908,909],{"class":666}," cached",[387,911,670],{"class":653},[387,913,914],{"class":653}," await",[387,916,917],{"class":628}," caches.",[387,919,920],{"class":632},"match",[387,922,923],{"class":628},"(request);\n",[387,925,926,928,931,934],{"class":133,"line":736},[387,927,739],{"class":653},[387,929,930],{"class":628}," (cached) ",[387,932,933],{"class":653},"return",[387,935,936],{"class":628}," cached;\n",[387,938,939,941,944,946,948,951],{"class":133,"line":745},[387,940,663],{"class":653},[387,942,943],{"class":666}," res",[387,945,670],{"class":653},[387,947,914],{"class":653},[387,949,950],{"class":632}," fetch",[387,952,923],{"class":628},[387,954,955,957,960,962,964,966,969],{"class":133,"line":766},[387,956,663],{"class":653},[387,958,959],{"class":666}," cache",[387,961,670],{"class":653},[387,963,914],{"class":653},[387,965,917],{"class":628},[387,967,968],{"class":632},"open",[387,970,971],{"class":628},"(cacheName);\n",[387,973,974,977,980,983,986],{"class":133,"line":804},[387,975,976],{"class":628},"  cache.",[387,978,979],{"class":632},"put",[387,981,982],{"class":628},"(request, res.",[387,984,985],{"class":632},"clone",[387,987,988],{"class":628},"());\n",[387,990,991,994],{"class":133,"line":822},[387,992,993],{"class":653},"  return",[387,995,996],{"class":628}," res;\n",[387,998,999],{"class":133,"line":828},[387,1000,1001],{"class":762},"  \u002F\u002F trade-off: never revalidates — only safe for content-hashed URLs. Apply\n",[387,1003,1004],{"class":133,"line":834},[387,1005,1006],{"class":762},"  \u002F\u002F this to index.html and users will be stuck on an old build forever.\n",[387,1008,1009],{"class":133,"line":840},[387,1010,1011],{"class":628},"}\n",[14,1013,1014],{},"Expected outcome: zero background requests for immutable assets, cutting per-hit bandwidth to nothing and keeping the INP path clear.",[557,1016,1018],{"id":1017},"fix-3-stale-while-revalidate-for-the-mutable-shell","Fix 3 — Stale-while-revalidate for the mutable shell",[14,1020,1021,1022,1024,1025,1027,1028,727],{},"Use SWR for resources that share a stable URL but whose body changes — the unhashed ",[29,1023,519],{},", a ",[29,1026,523],{},", or a user avatar at ",[29,1029,1030],{},"\u002Fme\u002Favatar.png",[615,1032,1034],{"className":617,"code":1033,"language":619,"meta":620,"style":620},"\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 immutable\n  \u002F\u002F hashed assets — it burns bandwidth refetching bytes that cannot have changed.\n}\n",[29,1035,1036,1041,1065,1081,1098,1112,1132,1148,1155,1160,1178,1191,1196,1202],{"__ignoreMap":620},[387,1037,1038],{"class":133,"line":625},[387,1039,1040],{"class":762},"\u002F\u002F SWR: serve cached immediately, refresh in the background for next time.\n",[387,1042,1043,1045,1047,1050,1052,1054,1056,1058,1060,1063],{"class":133,"line":660},[387,1044,878],{"class":653},[387,1046,881],{"class":653},[387,1048,1049],{"class":632}," staleWhileRevalidate",[387,1051,636],{"class":628},[387,1053,889],{"class":646},[387,1055,520],{"class":628},[387,1057,894],{"class":646},[387,1059,670],{"class":653},[387,1061,1062],{"class":639}," 'shell-v1'",[387,1064,902],{"class":628},[387,1066,1067,1069,1071,1073,1075,1077,1079],{"class":133,"line":682},[387,1068,663],{"class":653},[387,1070,959],{"class":666},[387,1072,670],{"class":653},[387,1074,914],{"class":653},[387,1076,917],{"class":628},[387,1078,968],{"class":632},[387,1080,971],{"class":628},[387,1082,1083,1085,1087,1089,1091,1094,1096],{"class":133,"line":736},[387,1084,663],{"class":653},[387,1086,909],{"class":666},[387,1088,670],{"class":653},[387,1090,914],{"class":653},[387,1092,1093],{"class":628}," cache.",[387,1095,920],{"class":632},[387,1097,923],{"class":628},[387,1099,1100,1102,1105,1107,1109],{"class":133,"line":745},[387,1101,663],{"class":653},[387,1103,1104],{"class":666}," network",[387,1106,670],{"class":653},[387,1108,950],{"class":632},[387,1110,1111],{"class":628},"(request)\n",[387,1113,1114,1117,1120,1123,1126,1128,1130],{"class":133,"line":766},[387,1115,1116],{"class":628},"    .",[387,1118,1119],{"class":632},"then",[387,1121,1122],{"class":628},"((",[387,1124,1125],{"class":646},"res",[387,1127,650],{"class":628},[387,1129,654],{"class":653},[387,1131,657],{"class":628},[387,1133,1134,1137,1140,1142,1144,1146],{"class":133,"line":804},[387,1135,1136],{"class":653},"      if",[387,1138,1139],{"class":628}," (res.ok) cache.",[387,1141,979],{"class":632},[387,1143,982],{"class":628},[387,1145,985],{"class":632},[387,1147,988],{"class":628},[387,1149,1150,1153],{"class":133,"line":822},[387,1151,1152],{"class":653},"      return",[387,1154,996],{"class":628},[387,1156,1157],{"class":133,"line":828},[387,1158,1159],{"class":628},"    })\n",[387,1161,1162,1164,1167,1170,1172,1175],{"class":133,"line":834},[387,1163,1116],{"class":628},[387,1165,1166],{"class":632},"catch",[387,1168,1169],{"class":628},"(() ",[387,1171,654],{"class":653},[387,1173,1174],{"class":628}," cached); ",[387,1176,1177],{"class":762},"\u002F\u002F offline: fall back to whatever we had\n",[387,1179,1180,1182,1185,1188],{"class":133,"line":840},[387,1181,993],{"class":653},[387,1183,1184],{"class":628}," cached ",[387,1186,1187],{"class":653},"||",[387,1189,1190],{"class":628}," network;\n",[387,1192,1193],{"class":133,"line":846},[387,1194,1195],{"class":762},"  \u002F\u002F trade-off: issues a network request on every hit. Do NOT use for immutable\n",[387,1197,1199],{"class":133,"line":1198},13,[387,1200,1201],{"class":762},"  \u002F\u002F hashed assets — it burns bandwidth refetching bytes that cannot have changed.\n",[387,1203,1205],{"class":133,"line":1204},14,[387,1206,1011],{"class":628},[14,1208,1209],{},"Expected outcome: the cache silently heals itself within one visit without an explicit purge, so a new shell rolls out without a manual invalidation.",[557,1211,1213],{"id":1212},"fix-4-guard-the-swr-state-update-during-hydration","Fix 4 — Guard the SWR state update during hydration",[14,1215,1216],{},"When an SWR route feeds React state, gate the update behind a content check so an unchanged background response cannot force a re-render mid-hydration.",[615,1218,1220],{"className":617,"code":1219,"language":619,"meta":620,"style":620},"\u002F\u002F In the page: only commit if the background response is genuinely newer.\nasync function refreshConfig(setConfig, lastETag) {\n  const res = await fetch('\u002Fconfig.json');\n  if (res.headers.get('ETag') === lastETag) return; \u002F\u002F unchanged → no re-render\n  setConfig(await res.json());\n  \u002F\u002F trade-off: relies on the origin sending a stable ETag. Without one, fall\n  \u002F\u002F back to a shallow deep-equal on the parsed body before calling setConfig —\n  \u002F\u002F never dispatch the raw response unconditionally during hydration.\n}\n",[29,1221,1222,1227,1248,1268,1298,1316,1321,1326,1331],{"__ignoreMap":620},[387,1223,1224],{"class":133,"line":625},[387,1225,1226],{"class":762},"\u002F\u002F In the page: only commit if the background response is genuinely newer.\n",[387,1228,1229,1231,1233,1236,1238,1241,1243,1246],{"class":133,"line":660},[387,1230,878],{"class":653},[387,1232,881],{"class":653},[387,1234,1235],{"class":632}," refreshConfig",[387,1237,636],{"class":628},[387,1239,1240],{"class":646},"setConfig",[387,1242,520],{"class":628},[387,1244,1245],{"class":646},"lastETag",[387,1247,902],{"class":628},[387,1249,1250,1252,1254,1256,1258,1260,1262,1265],{"class":133,"line":682},[387,1251,663],{"class":653},[387,1253,943],{"class":666},[387,1255,670],{"class":653},[387,1257,914],{"class":653},[387,1259,950],{"class":632},[387,1261,636],{"class":628},[387,1263,1264],{"class":639},"'\u002Fconfig.json'",[387,1266,1267],{"class":628},");\n",[387,1269,1270,1272,1275,1278,1280,1283,1285,1287,1290,1292,1295],{"class":133,"line":736},[387,1271,739],{"class":653},[387,1273,1274],{"class":628}," (res.headers.",[387,1276,1277],{"class":632},"get",[387,1279,636],{"class":628},[387,1281,1282],{"class":639},"'ETag'",[387,1284,650],{"class":628},[387,1286,781],{"class":653},[387,1288,1289],{"class":628}," lastETag) ",[387,1291,933],{"class":653},[387,1293,1294],{"class":628},"; ",[387,1296,1297],{"class":762},"\u002F\u002F unchanged → no re-render\n",[387,1299,1300,1303,1305,1308,1311,1314],{"class":133,"line":745},[387,1301,1302],{"class":632},"  setConfig",[387,1304,636],{"class":628},[387,1306,1307],{"class":653},"await",[387,1309,1310],{"class":628}," res.",[387,1312,1313],{"class":632},"json",[387,1315,988],{"class":628},[387,1317,1318],{"class":133,"line":766},[387,1319,1320],{"class":762},"  \u002F\u002F trade-off: relies on the origin sending a stable ETag. Without one, fall\n",[387,1322,1323],{"class":133,"line":804},[387,1324,1325],{"class":762},"  \u002F\u002F back to a shallow deep-equal on the parsed body before calling setConfig —\n",[387,1327,1328],{"class":133,"line":822},[387,1329,1330],{"class":762},"  \u002F\u002F never dispatch the raw response unconditionally during hydration.\n",[387,1332,1333],{"class":133,"line":828},[387,1334,1011],{"class":628},[14,1336,1337],{},"Expected outcome: unchanged revalidations no longer trigger a render pass, removing the SWR-specific INP spike from the p75.",[557,1339,1341],{"id":1340},"fix-5-network-first-for-freshness-critical-data","Fix 5 — Network-first for freshness-critical data",[14,1343,1344],{},"For cart totals, auth state, and prices, neither cached strategy is correct. Serve network-first with a cache fallback so a stale value never reaches the user while online.",[615,1346,1348],{"className":617,"code":1347,"language":619,"meta":620,"style":620},"async function networkFirst(request, cacheName = 'data-v1') {\n  try {\n    const res = await fetch(request);\n    const cache = await caches.open(cacheName);\n    cache.put(request, res.clone());\n    return res;\n  } catch {\n    return caches.match(request); \u002F\u002F offline only: last-known value\n  }\n  \u002F\u002F trade-off: every hit waits on the network, so this is the slowest path.\n  \u002F\u002F Reserve it for data where a stale value is a correctness bug, not for the shell.\n}\n",[29,1349,1350,1374,1381,1396,1412,1425,1432,1440,1454,1458,1463,1468],{"__ignoreMap":620},[387,1351,1352,1354,1356,1359,1361,1363,1365,1367,1369,1372],{"class":133,"line":625},[387,1353,878],{"class":653},[387,1355,881],{"class":653},[387,1357,1358],{"class":632}," networkFirst",[387,1360,636],{"class":628},[387,1362,889],{"class":646},[387,1364,520],{"class":628},[387,1366,894],{"class":646},[387,1368,670],{"class":653},[387,1370,1371],{"class":639}," 'data-v1'",[387,1373,902],{"class":628},[387,1375,1376,1379],{"class":133,"line":660},[387,1377,1378],{"class":653},"  try",[387,1380,657],{"class":628},[387,1382,1383,1386,1388,1390,1392,1394],{"class":133,"line":682},[387,1384,1385],{"class":653},"    const",[387,1387,943],{"class":666},[387,1389,670],{"class":653},[387,1391,914],{"class":653},[387,1393,950],{"class":632},[387,1395,923],{"class":628},[387,1397,1398,1400,1402,1404,1406,1408,1410],{"class":133,"line":736},[387,1399,1385],{"class":653},[387,1401,959],{"class":666},[387,1403,670],{"class":653},[387,1405,914],{"class":653},[387,1407,917],{"class":628},[387,1409,968],{"class":632},[387,1411,971],{"class":628},[387,1413,1414,1417,1419,1421,1423],{"class":133,"line":745},[387,1415,1416],{"class":628},"    cache.",[387,1418,979],{"class":632},[387,1420,982],{"class":628},[387,1422,985],{"class":632},[387,1424,988],{"class":628},[387,1426,1427,1430],{"class":133,"line":766},[387,1428,1429],{"class":653},"    return",[387,1431,996],{"class":628},[387,1433,1434,1436,1438],{"class":133,"line":804},[387,1435,769],{"class":628},[387,1437,1166],{"class":653},[387,1439,657],{"class":628},[387,1441,1442,1444,1446,1448,1451],{"class":133,"line":822},[387,1443,1429],{"class":653},[387,1445,917],{"class":628},[387,1447,920],{"class":632},[387,1449,1450],{"class":628},"(request); ",[387,1452,1453],{"class":762},"\u002F\u002F offline only: last-known value\n",[387,1455,1456],{"class":133,"line":828},[387,1457,825],{"class":628},[387,1459,1460],{"class":133,"line":834},[387,1461,1462],{"class":762},"  \u002F\u002F trade-off: every hit waits on the network, so this is the slowest path.\n",[387,1464,1465],{"class":133,"line":840},[387,1466,1467],{"class":762},"  \u002F\u002F Reserve it for data where a stale value is a correctness bug, not for the shell.\n",[387,1469,1470],{"class":133,"line":846},[387,1471,1011],{"class":628},[14,1473,1474],{},"Expected outcome: freshness-critical routes always reflect the origin online, with a graceful last-known fallback only when offline.",[14,1476,1477,1478,520,1481,1484,1485,1488,1489,1492,1493,727],{},"If you are on Workbox rather than a hand-rolled worker, the same split maps directly onto ",[29,1479,1480],{},"CacheFirst",[29,1482,1483],{},"StaleWhileRevalidate",", and ",[29,1486,1487],{},"NetworkFirst"," route handlers; the matrix above still governs which to register per ",[29,1490,1491],{},"registerRoute",". The deeper question cache-first eventually forces — how those pinned assets ever get refreshed — is covered in ",[18,1494,1496],{"href":1495},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002F","cache invalidation patterns",[47,1498,1500],{"id":1499},"verification","Verification",[14,1502,1503],{},"Confirm each route resolves with the strategy you intended, then lock the split in CI.",[1505,1506,1507,1516,1530,1543],"ul",{},[468,1508,1509,1512,1513,1515],{},[471,1510,1511],{},"Network initiator diff."," With ",[471,1514,477],{}," on, reload twice: hashed assets must show zero background requests; the app shell must show exactly one. That before\u002Fafter — a shared bucket refetching everything versus a clean split — is the primary signal the routing took.",[468,1517,1518,1521,1522,1525,1526,1529],{},[471,1519,1520],{},"Offline reload."," Throttle to ",[471,1523,1524],{},"Offline"," and reload. Both cached strategies must still paint; if SWR fails, your background-fetch ",[29,1527,1528],{},".catch"," fallback is missing.",[468,1531,1532,1535,1536,1538,1539,1542],{},[471,1533,1534],{},"Long-task check."," Record a Performance reload and confirm no service-worker callback creates a task over the 50ms long-task budget — an oversized synchronous ",[29,1537,547],{}," under SWR is the usual culprit. Defer it with ",[29,1540,1541],{},"event.waitUntil"," so it runs off the critical path.",[468,1544,1545,1548],{},[471,1546,1547],{},"CI assertion."," Fail the build if the worker audit regresses, so a future refactor cannot silently reintroduce a catch-all strategy.",[615,1550,1552],{"className":617,"code":1551,"language":619,"meta":620,"style":620},"module.exports = {\n  ci: { assert: { assertions: { 'service-worker': 'error' } } },\n};\n\u002F\u002F trade-off: this asserts a worker is registered and controls the page, not\n\u002F\u002F that the per-route split is correct. Pair it with an integration test that\n\u002F\u002F asserts a hashed asset fires no second request and the shell fires exactly one.\n",[29,1553,1554,1568,1585,1590,1595,1600],{"__ignoreMap":620},[387,1555,1556,1559,1561,1564,1566],{"class":133,"line":625},[387,1557,1558],{"class":666},"module",[387,1560,727],{"class":628},[387,1562,1563],{"class":666},"exports",[387,1565,670],{"class":653},[387,1567,657],{"class":628},[387,1569,1570,1573,1576,1579,1582],{"class":133,"line":660},[387,1571,1572],{"class":628},"  ci: { assert: { assertions: { ",[387,1574,1575],{"class":639},"'service-worker'",[387,1577,1578],{"class":628},": ",[387,1580,1581],{"class":639},"'error'",[387,1583,1584],{"class":628}," } } },\n",[387,1586,1587],{"class":133,"line":682},[387,1588,1589],{"class":628},"};\n",[387,1591,1592],{"class":133,"line":736},[387,1593,1594],{"class":762},"\u002F\u002F trade-off: this asserts a worker is registered and controls the page, not\n",[387,1596,1597],{"class":133,"line":745},[387,1598,1599],{"class":762},"\u002F\u002F that the per-route split is correct. Pair it with an integration test that\n",[387,1601,1602],{"class":133,"line":766},[387,1603,1604],{"class":762},"\u002F\u002F asserts a hashed asset fires no second request and the shell fires exactly one.\n",[1505,1606,1607],{},[468,1608,1609,1612,1613,1615],{},[471,1610,1611],{},"RUM field validation."," Watch INP p75 for SWR routes after rollout; a regression above 200ms means a background ",[29,1614,547],{}," or an unguarded state update is contending with interaction work — revisit Fix 4. Choosing per route, rather than globally, is what keeps a React SPA both fast and fresh.",[47,1617,1619],{"id":1618},"related","Related",[1505,1621,1622,1630,1637,1644,1650],{},[468,1623,1624,1626,1627,1629],{},[18,1625,21],{"href":20}," — the parent guide on ",[29,1628,31],{}," interception and strategy selection.",[468,1631,1632,1636],{},[18,1633,1635],{"href":1634},"\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.",[468,1638,1639,1643],{},[18,1640,1642],{"href":1641},"\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.",[468,1645,1646,1649],{},[18,1647,1648],{"href":1495},"Cache invalidation patterns"," — how cache-first resources eventually get refreshed.",[468,1651,1652,1655],{},[18,1653,1654],{"href":25},"Advanced Caching Strategies & CDN Architecture"," — the full caching architecture this fits into.",[1657,1658,1660],"script",{"type":1659},"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 per-route decision guide 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-07-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",[1657,1662,1663],{"type":1659},"\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  \"description\": \"Route hashed assets to cache-first and the mutable shell to stale-while-revalidate, guard the SWR state update during hydration, and reserve network-first for freshness-critical data.\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-07-18\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"name\": \"Route per resource, not per application\", \"text\": \"Split the fetch handler so hashed URLs go cache-first and the mutable HTML shell goes stale-while-revalidate.\", \"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#fix-1-route-per-resource-not-per-application-highest-impact\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Cache-first for content-hashed assets\", \"text\": \"Serve content-hashed bundles, fonts, and immutable media from a cache-first handler that never revalidates.\", \"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#fix-2-cache-first-for-content-hashed-assets\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Stale-while-revalidate for the mutable shell\", \"text\": \"Serve the unhashed index.html, config JSON, and avatars stale-while-revalidate so they refresh within one visit.\", \"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#fix-3-stale-while-revalidate-for-the-mutable-shell\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Guard the SWR state update during hydration\", \"text\": \"Gate any React state update from a background revalidation behind an ETag or equality check to avoid a mid-hydration re-render.\", \"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#fix-4-guard-the-swr-state-update-during-hydration\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Network-first for freshness-critical data\", \"text\": \"Serve cart totals, auth state, and prices network-first with a cache fallback so a stale value never renders online.\", \"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#fix-5-network-first-for-freshness-critical-data\" }\n  ]\n}\n",[1657,1665,1666],{"type":1659},"\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",[1668,1669,1670],"style",{},"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 pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}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":620,"searchDepth":660,"depth":660,"links":1672},[1673,1674,1675,1676,1682,1689,1690],{"id":49,"depth":660,"text":50},{"id":186,"depth":660,"text":187},{"id":459,"depth":660,"text":460},{"id":554,"depth":660,"text":555,"children":1677},[1678,1679,1680,1681],{"id":559,"depth":682,"text":560},{"id":572,"depth":682,"text":573},{"id":585,"depth":682,"text":586},{"id":592,"depth":682,"text":593},{"id":602,"depth":660,"text":603,"children":1683},[1684,1685,1686,1687,1688],{"id":609,"depth":682,"text":610},{"id":859,"depth":682,"text":860},{"id":1017,"depth":682,"text":1018},{"id":1212,"depth":682,"text":1213},{"id":1340,"depth":682,"text":1341},{"id":1499,"depth":660,"text":1500},{"id":1618,"depth":660,"text":1619},"A per-route decision guide comparing stale-while-revalidate and cache-first Service Worker strategies for React single-page applications.","md",{"slug":12,"type":1694,"breadcrumb":1695,"datePublished":1703,"dateModified":1704},"article",[1696,1698,1699,1701],{"name":1697,"url":724},"Home",{"name":1654,"url":25},{"name":1700,"url":20},"Service Worker Caching Strategies",{"name":5,"url":1702},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002F","2026-06-18","2026-07-18",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas",{"title":1708,"description":1709},"SWR vs Cache-First Service Worker (React SPAs)","Choose stale-while-revalidate or cache-first Service Worker strategies per route in a React SPA: a decision matrix, named failure modes, and paste-ready fixes.","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fswr-vs-cache-first-service-worker-for-react-spas\u002Findex","IFxiHfgf0Y2-DmrsOXgx5a7pPz1jIqeDrLzBvHUFQHA",[1713,1717],{"title":1714,"path":1715,"stem":1716},"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":1718,"path":1719,"stem":1720},"Stale-While-Revalidate Implementation","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation","advanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Findex",1784398534522]