[{"data":1,"prerenderedAt":1023},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely":1014},{"id":4,"title":5,"body":6,"description":994,"extension":995,"meta":996,"navigation":469,"path":1008,"seo":1009,"stem":1012,"__hash__":1013},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely\u002Findex.md","Caching Opaque Responses Safely",{"type":7,"value":8,"toc":977},"minimark",[9,14,39,42,155,160,203,207,213,222,236,242,293,297,302,313,392,396,605,609,626,630,633,750,754,757,761,772,776,783,787,816,820,826,838,844,853,857,869,878,887,896,923,935,939,962,967,970,973],[10,11,13],"h1",{"id":12},"how-to-cache-opaque-responses-safely-in-a-service-worker","How to Cache Opaque Responses Safely in a Service Worker",[15,16,17,18,23,24,28,29,33,34,38],"p",{},"This guide covers a cross-origin pitfall in ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F","Service Worker Caching Strategies",", part of ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". When a page requests a cross-origin resource without CORS — an image from a third-party CDN, a script tag without ",[30,31,32],"code",{},"crossorigin",", a font from another host — and a service worker intercepts it, the worker receives an ",[35,36,37],"em",{},"opaque"," response. It can pass the response to the page and store it in Cache Storage, but it cannot read its status, headers or body.",[15,40,41],{},"Two consequences make opaque responses dangerous to cache. First, the worker cannot tell success from failure: a 404 or 500 looks the same as a 200, so a cache-first strategy may store an error and serve it indefinitely. Second, browsers pad opaque responses' reported sizes to prevent cross-origin size leaks — Chromium counts each opaque entry as several megabytes against the origin's storage quota — so caching a few hundred third-party images can exhaust quota and evict your important caches.",[15,43,44],{},[45,46,52,53,52,60,52,64,52,67,52,76,52,82,52,91,52,97,52,102,52,107,52,110,52,113,52,116,52,119,52,122,52,125,52,130,52,134,52,136,52,140,52,142,52,145,52,147,52,150,52,152,52],"svg",{"viewBox":47,"width":48,"role":49,"ariaLabel":50,"style":51},"0 0 760 228","100%","img","Comparison of what a service worker can see and what it costs to cache a CORS-enabled response versus an opaque no-cors response.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[54,55],"rect",{"className":56,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],"svg-canvas","0","#ffffff",[61,62,63],"title",{},"CORS response vs opaque response in the cache",[65,66,50],"desc",{},[54,68],{"x":69,"y":69,"width":70,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"1","758","226","10","none","currentColor","stroke-opacity:0.18",[77,78,63],"text",{"x":79,"y":80,"fill":74,"style":81},"28.0","34.0","font-size:16px;font-weight:700",[54,83],{"x":79,"y":84,"width":85,"height":86,"rx":87,"fill":88,"stroke":89,"style":90},"56.0","340.0","150.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[77,92,96],{"x":93,"y":94,"fill":74,"style":95},"42.0","82.0","font-size:14px;font-weight:700","CORS response",[77,98,101],{"x":93,"y":99,"fill":74,"style":100},"108.0","font-size:12.5px;font-weight:700","•",[77,103,106],{"x":84,"y":99,"fill":74,"style":104,"textAnchor":105},"font-size:12.5px","start","status, headers and body readable",[77,108,101],{"x":93,"y":109,"fill":74,"style":100},"132.0",[77,111,112],{"x":84,"y":109,"fill":74,"style":104,"textAnchor":105},"Errors can be detected and skipped",[77,114,101],{"x":93,"y":115,"fill":74,"style":100},"156.0",[77,117,118],{"x":84,"y":115,"fill":74,"style":104,"textAnchor":105},"Quota usage = real size",[77,120,101],{"x":93,"y":121,"fill":74,"style":100},"180.0",[77,123,124],{"x":84,"y":121,"fill":74,"style":104,"textAnchor":105},"Safe for cache-first",[54,126],{"x":127,"y":84,"width":85,"height":86,"rx":87,"fill":128,"stroke":128,"style":129},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[77,131,133],{"x":132,"y":94,"fill":74,"style":95},"406.0","Opaque (no-cors) response",[77,135,101],{"x":132,"y":99,"fill":74,"style":100},[77,137,139],{"x":138,"y":99,"fill":74,"style":104,"textAnchor":105},"420.0","status reported as 0, nothing readable",[77,141,101],{"x":132,"y":109,"fill":74,"style":100},[77,143,144],{"x":138,"y":109,"fill":74,"style":104,"textAnchor":105},"Errors cached as if successful",[77,146,101],{"x":132,"y":115,"fill":74,"style":100},[77,148,149],{"x":138,"y":115,"fill":74,"style":104,"textAnchor":105},"Quota usage padded to megabytes",[77,151,101],{"x":132,"y":121,"fill":74,"style":100},[77,153,154],{"x":138,"y":121,"fill":74,"style":104,"textAnchor":105},"Only safe with network-first or SWR",[156,157,159],"h2",{"id":158},"rapid-diagnosis","Rapid Diagnosis",[161,162,163,171,177,183],"ul",{},[164,165,166,170],"li",{},[167,168,169],"strong",{},"Inspect Cache Storage entries."," In DevTools, cached cross-origin responses with type \"opaque\" are the ones to examine.",[164,172,173,176],{},[167,174,175],{},"Check quota usage."," Application → Storage shows usage; a few hundred megabytes from a modest number of entries indicates opaque padding.",[164,178,179,182],{},[167,180,181],{},"Check for cached failures."," Broken images that persist across reloads even when the third-party host is up suggest a cached opaque error.",[164,184,185,52,188,191,192,194,195,198,199,202],{},[167,186,187],{},"Check request modes.",[30,189,190],{},"\u003Cimg>"," without ",[30,193,32],{},", CSS ",[30,196,197],{},"url()"," to other origins and classic ",[30,200,201],{},"\u003Cscript>"," tags produce no-cors requests.",[156,204,206],{"id":205},"root-cause-analysis","Root Cause Analysis",[15,208,209,212],{},[167,210,211],{},"1. Runtime caching rules that match cross-origin URLs."," A rule like \"cache all images cache-first\" catches third-party images too.",[15,214,215,218,219,221],{},[167,216,217],{},"2. No-cors requests by default."," Elements fetch cross-origin resources without CORS unless marked with ",[30,220,32],{},".",[15,223,224,227,228,231,232,235],{},[167,225,226],{},"3. Status checks that fail open."," Code that caches when ",[30,229,230],{},"response.ok"," is false-y does not apply to opaque responses (status 0), and code that checks ",[30,233,234],{},"response.type"," is rare.",[15,237,238,241],{},[167,239,240],{},"4. Padding by design."," Browsers intentionally overstate opaque sizes; quota fills much faster than real bytes suggest.",[15,243,244],{},[45,245,52,248,52,251,52,254,52,256,52,259,52,261,52,268,52,275,52,280,52,284,52,289,52],{"viewBox":246,"width":48,"role":49,"ariaLabel":247,"style":51},"0 0 760 132","Bar chart of storage quota consumed by three hundred cached images when stored as CORS responses versus opaque responses.",[54,249],{"className":250,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],[61,252,253],{},"Quota used by 300 cached images",[65,255,247],{},[54,257],{"x":69,"y":69,"width":70,"height":258,"rx":72,"fill":73,"stroke":74,"style":75},"130",[77,260,253],{"x":79,"y":80,"fill":74,"style":81},[77,262,267],{"x":263,"y":264,"fill":74,"style":265,"textAnchor":266},"193.6","70.0","font-size:13px","end","300 CORS images (real size)",[54,269],{"x":270,"y":84,"width":271,"height":272,"rx":273,"fill":128,"stroke":128,"style":274},"205.6","5.3","19","3","fill-opacity:0.55;stroke-opacity:0.9",[77,276,279],{"x":277,"y":264,"fill":74,"style":278},"216.9","font-size:12px;font-weight:600","24MB",[77,281,283],{"x":263,"y":282,"fill":74,"style":265,"textAnchor":266},"101.0","300 opaque images (padded)",[54,285],{"x":270,"y":286,"width":287,"height":272,"rx":273,"fill":88,"stroke":89,"style":288},"87.0","462.4","fill-opacity:0.7;stroke-opacity:0.9",[77,290,292],{"x":291,"y":282,"fill":74,"style":278},"674.0","2100MB",[156,294,296],{"id":295},"step-by-step-resolution","Step-by-Step Resolution",[298,299,301],"h3",{"id":300},"_1-request-cross-origin-assets-with-cors-where-the-server-allows-it","1. Request cross-origin assets with CORS where the server allows it",[15,303,304,305,308,309,312],{},"Add ",[30,306,307],{},"crossorigin=\"anonymous\""," to images, scripts and preloads from hosts that send ",[30,310,311],{},"Access-Control-Allow-Origin",", so the worker receives readable CORS responses.",[314,315,320],"pre",{"className":316,"code":317,"language":318,"meta":319,"style":319},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cimg src=\"https:\u002F\u002Fimages.example-cdn.com\u002Fp\u002F42.avif\" crossorigin=\"anonymous\" width=\"800\" height=\"800\" alt=\"\">\n\u003C!-- trade-off: if the host does NOT send CORS headers, adding crossorigin makes\n     the image fail to load entirely. Verify the header before changing markup. -->\n","html","",[30,321,322,379,386],{"__ignoreMap":319},[323,324,327,331,334,338,341,345,348,350,353,356,358,361,364,366,368,371,373,376],"span",{"class":325,"line":326},"line",1,[323,328,330],{"class":329},"saISM","\u003C",[323,332,49],{"class":333},"sZBmE",[323,335,337],{"class":336},"sPXB4"," src",[323,339,340],{"class":329},"=",[323,342,344],{"class":343},"sZ8jY","\"https:\u002F\u002Fimages.example-cdn.com\u002Fp\u002F42.avif\"",[323,346,347],{"class":336}," crossorigin",[323,349,340],{"class":329},[323,351,352],{"class":343},"\"anonymous\"",[323,354,355],{"class":336}," width",[323,357,340],{"class":329},[323,359,360],{"class":343},"\"800\"",[323,362,363],{"class":336}," height",[323,365,340],{"class":329},[323,367,360],{"class":343},[323,369,370],{"class":336}," alt",[323,372,340],{"class":329},[323,374,375],{"class":343},"\"\"",[323,377,378],{"class":329},">\n",[323,380,382],{"class":325,"line":381},2,[323,383,385],{"class":384},"sjfSM","\u003C!-- trade-off: if the host does NOT send CORS headers, adding crossorigin makes\n",[323,387,389],{"class":325,"line":388},3,[323,390,391],{"class":384},"     the image fail to load entirely. Verify the header before changing markup. -->\n",[298,393,395],{"id":394},"_2-exclude-opaque-responses-from-cache-first-strategies","2. Exclude opaque responses from cache-first strategies",[314,397,401],{"className":398,"code":399,"language":400,"meta":319,"style":319},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { registerRoute } from 'workbox-routing';\nimport { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';\nimport { CacheableResponsePlugin } from 'workbox-cacheable-response';\nimport { ExpirationPlugin } from 'workbox-expiration';\n\nregisterRoute(({ request, url }) => request.destination === 'image' && url.origin === location.origin,\n  new CacheFirst({ cacheName: 'images', plugins: [\n    new CacheableResponsePlugin({ statuses: [200] }),\n    new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 30 * 86400, purgeOnQuotaError: true }),\n  ] }));\n\u002F\u002F trade-off: limiting cache-first to same-origin images leaves third-party\n\u002F\u002F images to the HTTP cache. That is usually fine — they rarely matter offline.\n","javascript",[30,402,403,421,435,449,464,471,517,535,553,587,593,599],{"__ignoreMap":319},[323,404,405,409,412,415,418],{"class":325,"line":326},[323,406,408],{"class":407},"sPARh","import",[323,410,411],{"class":329}," { registerRoute } ",[323,413,414],{"class":407},"from",[323,416,417],{"class":343}," 'workbox-routing'",[323,419,420],{"class":329},";\n",[323,422,423,425,428,430,433],{"class":325,"line":381},[323,424,408],{"class":407},[323,426,427],{"class":329}," { CacheFirst, StaleWhileRevalidate } ",[323,429,414],{"class":407},[323,431,432],{"class":343}," 'workbox-strategies'",[323,434,420],{"class":329},[323,436,437,439,442,444,447],{"class":325,"line":388},[323,438,408],{"class":407},[323,440,441],{"class":329}," { CacheableResponsePlugin } ",[323,443,414],{"class":407},[323,445,446],{"class":343}," 'workbox-cacheable-response'",[323,448,420],{"class":329},[323,450,452,454,457,459,462],{"class":325,"line":451},4,[323,453,408],{"class":407},[323,455,456],{"class":329}," { ExpirationPlugin } ",[323,458,414],{"class":407},[323,460,461],{"class":343}," 'workbox-expiration'",[323,463,420],{"class":329},[323,465,467],{"class":325,"line":466},5,[323,468,470],{"emptyLinePlaceholder":469},true,"\n",[323,472,474,478,481,485,488,491,494,497,500,503,506,509,512,514],{"class":325,"line":473},6,[323,475,477],{"class":476},"smZ65","registerRoute",[323,479,480],{"class":329},"(({ ",[323,482,484],{"class":483},"sQw3B","request",[323,486,487],{"class":329},", ",[323,489,490],{"class":483},"url",[323,492,493],{"class":329}," }) ",[323,495,496],{"class":407},"=>",[323,498,499],{"class":329}," request.destination ",[323,501,502],{"class":407},"===",[323,504,505],{"class":343}," 'image'",[323,507,508],{"class":407}," &&",[323,510,511],{"class":329}," url.origin ",[323,513,502],{"class":407},[323,515,516],{"class":329}," location.origin,\n",[323,518,520,523,526,529,532],{"class":325,"line":519},7,[323,521,522],{"class":407},"  new",[323,524,525],{"class":476}," CacheFirst",[323,527,528],{"class":329},"({ cacheName: ",[323,530,531],{"class":343},"'images'",[323,533,534],{"class":329},", plugins: [\n",[323,536,538,541,544,547,550],{"class":325,"line":537},8,[323,539,540],{"class":407},"    new",[323,542,543],{"class":476}," CacheableResponsePlugin",[323,545,546],{"class":329},"({ statuses: [",[323,548,549],{"class":336},"200",[323,551,552],{"class":329},"] }),\n",[323,554,556,558,561,564,566,569,572,575,578,581,584],{"class":325,"line":555},9,[323,557,540],{"class":407},[323,559,560],{"class":476}," ExpirationPlugin",[323,562,563],{"class":329},"({ maxEntries: ",[323,565,549],{"class":336},[323,567,568],{"class":329},", maxAgeSeconds: ",[323,570,571],{"class":336},"30",[323,573,574],{"class":407}," *",[323,576,577],{"class":336}," 86400",[323,579,580],{"class":329},", purgeOnQuotaError: ",[323,582,583],{"class":336},"true",[323,585,586],{"class":329}," }),\n",[323,588,590],{"class":325,"line":589},10,[323,591,592],{"class":329},"  ] }));\n",[323,594,596],{"class":325,"line":595},11,[323,597,598],{"class":384},"\u002F\u002F trade-off: limiting cache-first to same-origin images leaves third-party\n",[323,600,602],{"class":325,"line":601},12,[323,603,604],{"class":384},"\u002F\u002F images to the HTTP cache. That is usually fine — they rarely matter offline.\n",[298,606,608],{"id":607},"_3-if-you-must-cache-opaque-responses-use-a-revalidating-strategy-with-tight-limits","3. If you must cache opaque responses, use a revalidating strategy with tight limits",[15,610,611,614,615,618,619,622,623,221],{},[30,612,613],{},"StaleWhileRevalidate"," with ",[30,616,617],{},"statuses: [0, 200]"," refreshes entries on every use, so a cached error is replaced on the next successful fetch. Add strict ",[30,620,621],{},"maxEntries"," and ",[30,624,625],{},"purgeOnQuotaError",[298,627,629],{"id":628},"_4-self-host-critical-cross-origin-assets","4. Self-host critical cross-origin assets",[15,631,632],{},"Assets important for offline use or LCP — fonts, logos, hero images — are better served from your own origin, where caching is fully under your control.",[15,634,635,712,52,714],{},[45,636,52,638,52,641,52,644,52,646,52,648,52,650,52,655,52,660,52,664,52,669,52,672,52,676,52,680,52,684,52,686,52,690,52,692,52,695,52,698,52,702,52,704,52,707,52,709],{"viewBox":47,"width":48,"role":49,"ariaLabel":637,"style":51},"Recommended service worker handling of cross-origin resources depending on CORS support and importance.",[54,639],{"className":640,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],[61,642,643],{},"Strategy choice for cross-origin resources",[65,645,637],{},[54,647],{"x":69,"y":69,"width":70,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},[77,649,643],{"x":79,"y":80,"fill":74,"style":81},[54,651],{"x":79,"y":84,"width":652,"height":653,"rx":58,"fill":74,"stroke":74,"style":654},"201.8","30.0","fill-opacity:0.06;stroke-opacity:0.4",[77,656,659],{"x":657,"y":658,"fill":74,"style":100,"textAnchor":105},"38.0","75.5","Resource",[54,661],{"x":662,"y":84,"width":663,"height":653,"rx":58,"fill":74,"stroke":74,"style":654},"229.8","251.1",[77,665,668],{"x":666,"y":658,"fill":74,"style":100,"textAnchor":667},"355.4","middle","CORS available?",[54,670],{"x":671,"y":84,"width":663,"height":653,"rx":58,"fill":74,"stroke":74,"style":654},"480.9",[77,673,675],{"x":674,"y":658,"fill":74,"style":100,"textAnchor":667},"606.5","Recommended handling",[54,677],{"x":79,"y":678,"width":652,"height":653,"rx":58,"fill":73,"stroke":74,"style":679},"86.0","stroke-opacity:0.35",[77,681,683],{"x":657,"y":682,"fill":74,"style":100,"textAnchor":105},"105.5","Third-party images (decorative)",[54,685],{"x":662,"y":678,"width":663,"height":653,"rx":58,"fill":88,"stroke":89,"style":90},[77,687,689],{"x":666,"y":682,"fill":74,"style":688,"textAnchor":667},"font-size:12px","no",[54,691],{"x":671,"y":678,"width":663,"height":653,"rx":58,"fill":128,"stroke":128,"style":129},[77,693,694],{"x":674,"y":682,"fill":74,"style":688,"textAnchor":667},"do not intercept",[54,696],{"x":79,"y":697,"width":652,"height":653,"rx":58,"fill":73,"stroke":74,"style":679},"116.0",[77,699,701],{"x":657,"y":700,"fill":74,"style":100,"textAnchor":105},"135.5","Image CDN with CORS",[54,703],{"x":662,"y":697,"width":663,"height":653,"rx":58,"fill":128,"stroke":128,"style":129},[77,705,706],{"x":666,"y":700,"fill":74,"style":688,"textAnchor":667},"yes",[54,708],{"x":671,"y":697,"width":663,"height":653,"rx":58,"fill":128,"stroke":128,"style":129},[77,710,711],{"x":674,"y":700,"fill":74,"style":688,"textAnchor":667},"cache-first, statuses ",[323,713,549],{},[54,715,52,719,52,723],{"x":79,"y":716,"width":717,"height":718,"rx":58,"fill":73,"stroke":74,"style":679},"146.0",201.8,30,[77,720,722],{"x":657,"y":721,"fill":74,"style":100,"text-anchor":105},"165.5","Fonts (CORS by spec)",[54,724,52,726,52,728],{"x":662,"y":716,"width":725,"height":718,"rx":58,"fill":128,"stroke":128,"style":129},251.1,[77,727,706],{"x":666,"y":721,"fill":74,"style":688,"text-anchor":667},[54,729,52,730,52,733],{"x":671,"y":716,"width":725,"height":718,"rx":58,"fill":128,"stroke":128,"style":129},[77,731,732],{"x":674,"y":721,"fill":74,"style":688,"text-anchor":667},"cache-first with expiry",[54,734,52,736,52,740],{"x":79,"y":735,"width":717,"height":718,"rx":58,"fill":73,"stroke":74,"style":679},"176.0",[77,737,739],{"x":657,"y":738,"fill":74,"style":100,"text-anchor":105},"195.5","Third-party scripts",[54,741,52,742,52,745],{"x":662,"y":735,"width":725,"height":718,"rx":58,"fill":88,"stroke":89,"style":90},[77,743,744],{"x":666,"y":738,"fill":74,"style":688,"text-anchor":667},"often no",[54,746,52,747,52],{"x":671,"y":735,"width":725,"height":718,"rx":58,"fill":74,"stroke":74,"style":654},[77,748,749],{"x":674,"y":738,"fill":74,"style":688,"text-anchor":667},"do not cache; self-host if critical",[156,751,753],{"id":752},"verification","Verification",[15,755,756],{},"After changes, inspect Cache Storage: entries for cache-first routes should be type \"cors\" or \"basic\", not \"opaque\". Storage usage should reflect real sizes. Simulate a failing third-party host (DevTools request blocking) and confirm broken responses are not stored or are replaced on recovery.",[156,758,760],{"id":759},"worked-example-a-marketplace-pwa-running-out-of-quota","Worked Example: A Marketplace PWA Running Out of Quota",[15,762,763,764,766,767,614,769,771],{},"A marketplace PWA cached all images cache-first, including seller images from a third-party host without CORS. Within a week of use, some devices reported over 2GB of storage, the browser started evicting caches, and the app shell itself was occasionally evicted — making the PWA slower than the website. The team restricted cache-first to same-origin and CORS-enabled image hosts, added ",[30,765,32],{}," where the image CDN supported it, set ",[30,768,621],{},[30,770,625],{},", and left other third-party images to the HTTP cache. Storage stabilised under 60MB and shell evictions stopped.",[156,773,775],{"id":774},"auditing-existing-caches-in-the-field","Auditing Existing Caches in the Field",[15,777,778,779,782],{},"Users who installed an older worker may still have opaque entries clogging their storage. On activation of the fixed worker, iterate over runtime caches and delete opaque entries (or delete the old cache names entirely as part of versioning). Monitor ",[30,780,781],{},"navigator.storage.estimate()"," in RUM for controlled sessions — a falling usage distribution after release confirms the cleanup worked, and a rising one warns that a new rule is catching opaque responses again.",[156,784,786],{"id":785},"common-mistakes","Common Mistakes",[161,788,789,795,801,807],{},[164,790,791,794],{},[167,792,793],{},"Catch-all image routes."," Matching by destination alone includes every third-party image on the page.",[164,796,797,800],{},[167,798,799],{},"Allowing status 0 with cache-first."," Errors become permanent until the cache expires.",[164,802,803,806],{},[167,804,805],{},"No expiration plugin."," Runtime caches grow without bound.",[164,808,809,815],{},[167,810,811,812,814],{},"Adding ",[30,813,32],{}," without checking CORS headers."," Images then fail to load at all.",[156,817,819],{"id":818},"edge-cases","Edge Cases",[15,821,822,825],{},[167,823,824],{},"Fonts are always CORS."," Browsers fetch web fonts in CORS mode, so font responses are readable if the host sends the header (and fail to load otherwise).",[15,827,828,831,832,834,835,837],{},[167,829,830],{},"Images via CSS."," CSS ",[30,833,197],{}," requests cannot set ",[30,836,32],{}," per request; they are no-cors when cross-origin. Serve such images from your origin if caching matters.",[15,839,840,843],{},[167,841,842],{},"Videos and range requests."," Range responses are not cacheable with simple strategies; avoid caching media in the worker unless you implement range handling.",[15,845,846,849,850,852],{},[167,847,848],{},"Quota differences."," Browsers allocate quota differently (and Safari evicts more aggressively); limits and ",[30,851,625],{}," keep behaviour predictable.",[156,854,856],{"id":855},"faq","FAQ",[858,859,862,866],"details",{"className":860},[861],"faq-item",[863,864,865],"summary",{},"Why does Chrome report opaque responses as so large?",[15,867,868],{},"To prevent sites from inferring the size of cross-origin resources (a privacy and security leak), the browser adds random padding to the quota accounting of opaque responses. The padding is large enough that a few hundred entries consume gigabytes of quota.",[858,870,872,875],{"className":871},[861],[863,873,874],{},"Can the service worker convert an opaque response into a readable one?",[15,876,877],{},"No. Only the server's CORS headers make a cross-origin response readable. The worker can re-request the resource in CORS mode, which succeeds only if the server allows it.",[858,879,881,884],{"className":880},[861],[863,882,883],{},"Does the HTTP cache have the same problem?",[15,885,886],{},"No. The HTTP cache stores cross-origin responses normally under its own rules. Opaque padding applies to storage APIs like Cache Storage.",[858,888,890,893],{"className":889},[861],[863,891,892],{},"Is it ever reasonable to cache opaque responses?",[15,894,895],{},"For a small number of non-critical resources needed offline, with revalidation and strict limits, yes. As a general strategy, no.",[858,897,899,902],{"className":898},[861],[863,900,901],{},"How do I check response types in my own code?",[15,903,904,906,907,910,911,914,915,918,919,922],{},[30,905,234],{}," is ",[30,908,909],{},"'opaque'"," for no-cors cross-origin responses, ",[30,912,913],{},"'cors'"," for CORS responses and ",[30,916,917],{},"'basic'"," for same-origin ones. Check it before ",[30,920,921],{},"cache.put"," in custom workers.",[858,924,926,929],{"className":925},[861],[863,927,928],{},"What happens when quota is exceeded?",[15,930,931,932,934],{},"Writes fail, and the browser may evict entire origins' storage under pressure. ",[30,933,625],{}," lets Workbox clear designated caches first so critical ones survive.",[156,936,938],{"id":937},"related","Related",[161,940,941,948,955],{},[164,942,943,947],{},[19,944,946],{"href":945},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production\u002F","Debugging service worker cache misses in production"," — inspecting what is cached.",[164,949,950,954],{},[19,951,953],{"href":952},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Fself-hosting-third-party-scripts\u002F","Self-hosting third-party scripts"," — removing cross-origin dependencies.",[164,956,957,961],{},[19,958,960],{"href":959},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002F","Image CDNs and fetchpriority"," — image hosts and CORS headers.",[963,964,966],"script",{"type":965},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Cache Opaque Responses Safely in a Service Worker\",\n  \"description\": \"What opaque responses are, why they inflate cache quota and hide errors, and the CORS and strategy changes that make cross-origin caching safe.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Request cross-origin assets with CORS where the server allows it\",\n      \"text\": \"Add crossorigin=\\\"anonymous\\\" to images, scripts and preloads from hosts that send Access-Control-Allow-Origin, so the worker receives readable CORS responses.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Exclude opaque responses from cache-first strategies\",\n      \"text\": \"registerRoute(({ request, url }) => request.destination === 'image' && url.origin === location.origin, new CacheFirst({ cacheName: 'images', plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 30 * 86400, purgeOnQuotaError: true }), ] }\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"If you must cache opaque responses, use a revalidating strategy with tight limits\",\n      \"text\": \"StaleWhileRevalidate with statuses: [0, 200] refreshes entries on every use, so a cached error is replaced on the next successful fetch.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Self-host critical cross-origin assets\",\n      \"text\": \"Assets important for offline use or LCP — fonts, logos, hero images — are better served from your own origin, where caching is fully under your control.\"\n    }\n  ]\n}\n",[963,968,969],{"type":965},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Cache Opaque Responses Safely in a Service Worker\",\n  \"description\": \"What opaque responses are, why they inflate cache quota and hide errors, and the CORS and strategy changes that make cross-origin caching safe.\",\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\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely\u002F\"\n  }\n}\n",[963,971,972],{"type":965},"\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\": \"Service Worker Caching Strategies\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Caching Opaque Responses Safely\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely\u002F\"\n    }\n  ]\n}\n",[974,975,976],"style",{},"html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--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);}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}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}",{"title":319,"searchDepth":381,"depth":381,"links":978},[979,980,981,987,988,989,990,991,992,993],{"id":158,"depth":381,"text":159},{"id":205,"depth":381,"text":206},{"id":295,"depth":381,"text":296,"children":982},[983,984,985,986],{"id":300,"depth":388,"text":301},{"id":394,"depth":388,"text":395},{"id":607,"depth":388,"text":608},{"id":628,"depth":388,"text":629},{"id":752,"depth":381,"text":753},{"id":759,"depth":381,"text":760},{"id":774,"depth":381,"text":775},{"id":785,"depth":381,"text":786},{"id":818,"depth":381,"text":819},{"id":855,"depth":381,"text":856},{"id":937,"depth":381,"text":938},"What opaque responses are, why they inflate cache quota and hide errors, and the CORS and strategy changes that make cross-origin caching safe.","md",{"slug":997,"type":998,"breadcrumb":999,"datePublished":1007,"dateModified":1007},"caching-opaque-responses-safely","article",[1000,1003,1004,1005],{"name":1001,"url":1002},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1006},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely\u002F","2026-10-06","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely",{"title":1010,"description":1011},"Caching Opaque Responses in Service Workers Safely","Cross-origin no-cors responses are opaque — status unknown, size padded to megabytes. Learn why caching them fills quota and caches errors, and how to fix it.","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fcaching-opaque-responses-safely\u002Findex","ul3m5hBr8aDbcl53L9_bWd-WZV6V_UJpyXgj6HirVyE",[1015,1019],{"title":1016,"path":1017,"stem":1018},"Avoiding the Service Worker Startup Penalty","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Favoiding-the-service-worker-startup-penalty","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Favoiding-the-service-worker-startup-penalty\u002Findex",{"title":1020,"path":1021,"stem":1022},"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",1791308075214]