[{"data":1,"prerenderedAt":1477},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers":1468},{"id":4,"title":5,"body":6,"description":1448,"extension":1449,"meta":1450,"navigation":1462,"path":1463,"seo":1464,"stem":1466,"__hash__":1467},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002Findex.md","Using the Cache API in Cloudflare Workers",{"type":7,"value":8,"toc":1432},"minimark",[9,14,46,57,211,216,275,279,287,303,312,322,455,459,464,598,601,605,931,934,938,943,1117,1121,1134,1209,1213,1226,1230,1236,1240,1277,1281,1291,1297,1303,1315,1319,1342,1351,1363,1377,1390,1394,1417,1422,1425,1428],[10,11,13],"h1",{"id":12},"how-to-use-the-cache-api-in-cloudflare-workers","How to Use the Cache API in Cloudflare Workers",[15,16,17,18,23,24,28,29,33,34,37,38,41,42,45],"p",{},"This guide implements ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002F","Edge Compute & Dynamic Caching"," on one platform, within ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". Cloudflare Workers sit in front of the CDN cache and can control it in two ways: implicitly, by passing ",[30,31,32],"code",{},"cf"," options to ",[30,35,36],{},"fetch()"," (cache everything, override TTLs, set a custom cache key), and explicitly, through the Service-Worker-style Cache API — ",[30,39,40],{},"caches.default.match()"," and ",[30,43,44],{},"put()"," — which lets the Worker decide exactly what to store, under which key, for how long.",[15,47,48,49,52,53,56],{},"The explicit Cache API is what makes advanced patterns possible: caching responses from APIs that send no cache headers, caching POST-backed search results under a GET key, implementing stale-while-revalidate yourself, or storing transformed responses (personalised-at-the-edge variants, rewritten HTML). It also has behaviours that surprise people — it is local to a data centre, it respects certain headers on ",[30,50,51],{},"put",", and it does not work on ",[30,54,55],{},"workers.dev"," domains — that are worth knowing before you rely on it.",[15,58,59],{},[60,61,67,68,67,75,67,79,67,82,67,100,67,108,67,114,67,121,67,128,67,131,67,135,67,138,67,142,67,145,67,149,67,158,67,161,67,164,67,167,67,173,67,179,67,183,67,188,67,192,67,196,67,200,67,204,67,207,67],"svg",{"viewBox":62,"width":63,"role":64,"ariaLabel":65,"style":66},"0 0 760 330","100%","img","Sequence of a Worker checking the local edge cache, fetching from origin on a miss, returning the response and storing it in the background.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[69,70],"rect",{"className":71,"x":73,"y":73,"width":63,"height":63,"fill":74},[72],"svg-canvas","0","#ffffff",[76,77,78],"title",{},"Worker cache flow",[80,81,65],"desc",{},[83,84,85],"defs",{},[86,87,94],"marker",{"id":88,"viewBox":89,"refX":90,"refY":91,"markerWidth":92,"markerHeight":92,"orient":93},"fa221b2bd8","0 0 10 10","9","5","7","auto-start-reverse",[95,96],"path",{"d":97,"fill":98,"style":99},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[69,101],{"x":102,"y":102,"width":103,"height":104,"rx":105,"fill":106,"stroke":98,"style":107},"1","758","328","10","none","stroke-opacity:0.18",[109,110,78],"text",{"x":111,"y":112,"fill":98,"style":113},"28.0","34.0","font-size:16px;font-weight:700",[69,115],{"x":116,"y":117,"width":118,"height":112,"rx":119,"fill":98,"stroke":98,"style":120},"41.0","56.0","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[109,122,127],{"x":123,"y":124,"fill":98,"style":125,"textAnchor":126},"116.0","78.0","font-size:13px;font-weight:700","middle","Client",[69,129],{"x":130,"y":117,"width":118,"height":112,"rx":119,"fill":98,"stroke":98,"style":120},"217.0",[109,132,134],{"x":133,"y":124,"fill":98,"style":125,"textAnchor":126},"292.0","Worker",[69,136],{"x":137,"y":117,"width":118,"height":112,"rx":119,"fill":98,"stroke":98,"style":120},"393.0",[109,139,141],{"x":140,"y":124,"fill":98,"style":125,"textAnchor":126},"468.0","Edge cache",[69,143],{"x":144,"y":117,"width":118,"height":112,"rx":119,"fill":98,"stroke":98,"style":120},"569.0",[109,146,148],{"x":147,"y":124,"fill":98,"style":125,"textAnchor":126},"644.0","Origin",[150,151],"line",{"x1":123,"y1":152,"x2":123,"y2":153,"stroke":98,"strokeWidth":154,"strokeDashArray":155,"style":157},"90.0","308.0","1.5",[156,156],"4","stroke-opacity:0.35",[150,159],{"x1":133,"y1":152,"x2":133,"y2":153,"stroke":98,"strokeWidth":154,"strokeDashArray":160,"style":157},[156,156],[150,162],{"x1":140,"y1":152,"x2":140,"y2":153,"stroke":98,"strokeWidth":154,"strokeDashArray":163,"style":157},[156,156],[150,165],{"x1":147,"y1":152,"x2":147,"y2":153,"stroke":98,"strokeWidth":154,"strokeDashArray":166,"style":157},[156,156],[150,168],{"x1":123,"y1":169,"x2":170,"y2":169,"stroke":98,"strokeWidth":154,"style":171,"markerEnd":172},"124.0","290.0","stroke-opacity:0.6","url(#fa221b2bd8)",[109,174,178],{"x":175,"y":176,"fill":98,"style":177,"textAnchor":126},"204.0","117.0","font-size:12px","GET \u002Fapi\u002Fproducts",[150,180],{"x1":133,"y1":181,"x2":182,"y2":181,"stroke":98,"strokeWidth":154,"style":171,"markerEnd":172},"164.0","466.0",[109,184,187],{"x":185,"y":186,"fill":98,"style":177,"textAnchor":126},"380.0","157.0","match(key)",[150,189],{"x1":140,"y1":175,"x2":190,"y2":175,"stroke":98,"strokeWidth":154,"strokeDashArray":191,"style":171,"markerEnd":172},"294.0",[91,156],[109,193,195],{"x":185,"y":194,"fill":98,"style":177,"textAnchor":126},"197.0","miss",[150,197],{"x1":133,"y1":198,"x2":199,"y2":198,"stroke":98,"strokeWidth":154,"style":171,"markerEnd":172},"244.0","642.0",[109,201,203],{"x":140,"y":202,"fill":98,"style":177,"textAnchor":126},"237.0","fetch",[150,205],{"x1":133,"y1":206,"x2":182,"y2":206,"stroke":98,"strokeWidth":154,"style":171,"markerEnd":172},"284.0",[109,208,210],{"x":185,"y":209,"fill":98,"style":177,"textAnchor":126},"277.0","put via waitUntil",[212,213,215],"h2",{"id":214},"rapid-diagnosis","Rapid Diagnosis",[217,218,219,235,245,269],"ul",{},[220,221,222,226,227,230,231,234],"li",{},[223,224,225],"strong",{},"Check whether responses are being cached at all."," Look at ",[30,228,229],{},"cf-cache-status"," on responses (HIT, MISS, DYNAMIC, BYPASS). ",[30,232,233],{},"DYNAMIC"," means the response was not eligible for the cache under default rules.",[220,236,237,240,241,244],{},[223,238,239],{},"Check where the Worker runs."," The Cache API is unavailable on ",[30,242,243],{},"*.workers.dev","; test on a custom domain route.",[220,246,247,253,254,257,258,261,262,265,266,252],{},[223,248,249,250,252],{},"Check response headers on ",[30,251,51],{},"."," Responses with ",[30,255,256],{},"Set-Cookie",", ",[30,259,260],{},"Cache-Control: private"," or ",[30,263,264],{},"no-store"," are not stored by ",[30,267,268],{},"cache.put",[220,270,271,274],{},[223,272,273],{},"Check the key."," Query strings, headers and URL variations produce different keys; inconsistent keys look like a low hit rate.",[212,276,278],{"id":277},"root-cause-analysis","Root Cause Analysis",[15,280,281,284,285,252],{},[223,282,283],{},"1. Default rules skip HTML and JSON."," Without configuration, Cloudflare caches static file extensions but not HTML or API responses, which show as ",[30,286,233],{},[15,288,289,292,293,257,296,299,300,302],{},[223,290,291],{},"2. Uncacheable origin headers."," Origins often send ",[30,294,295],{},"private",[30,297,298],{},"no-cache"," or cookies; ",[30,301,51],{}," respects some of these and silently stores nothing.",[15,304,305,67,308,311],{},[223,306,307],{},"3. Per-data-centre caches.",[30,309,310],{},"caches.default"," is local to the data centre handling the request; a new location starts cold. Hit rates look lower than with tiered caching.",[15,313,314,317,318,321],{},[223,315,316],{},"4. Key fragmentation."," Tracking parameters, varying ",[30,319,320],{},"Accept"," headers and inconsistent trailing slashes split the cache.",[15,323,324],{},[60,325,67,328,67,331,67,334,67,336,67,339,67,341,67,345,67,352,67,356,67,360,67,363,67,367,67,370,67,374,67,378,67,381,67,383,67,386,67,388,67,392,67,394,67,397,67,399,67,402,67,405,67,409,67,414,67,417,67,419,67,422,67,425,67,429,67,431,67,433,67,435,67,438,67,441,67,445,67,447,67,450,67,452,67],{"viewBox":326,"width":63,"role":64,"ariaLabel":327,"style":66},"0 0 760 258","Comparison of caching through fetch with cf options and explicit caching with the Cache API.",[69,329],{"className":330,"x":73,"y":73,"width":63,"height":63,"fill":74},[72],[76,332,333],{},"Two ways to cache from a Worker",[80,335,327],{},[69,337],{"x":102,"y":102,"width":103,"height":338,"rx":105,"fill":106,"stroke":98,"style":107},"256",[109,340,333],{"x":111,"y":112,"fill":98,"style":113},[69,342],{"x":111,"y":117,"width":343,"height":344,"rx":73,"fill":98,"stroke":98,"style":120},"193.3","30.0",[109,346,351],{"x":347,"y":348,"fill":98,"style":349,"textAnchor":350},"38.0","75.5","font-size:12.5px;font-weight:700","start","Aspect",[69,353],{"x":354,"y":117,"width":355,"height":344,"rx":73,"fill":98,"stroke":98,"style":120},"221.3","255.4",[109,357,359],{"x":358,"y":348,"fill":98,"style":349,"textAnchor":126},"349.0","fetch() with cf options",[69,361],{"x":362,"y":117,"width":355,"height":344,"rx":73,"fill":98,"stroke":98,"style":120},"476.6",[109,364,366],{"x":365,"y":348,"fill":98,"style":349,"textAnchor":126},"604.3","caches.default API",[69,368],{"x":111,"y":369,"width":343,"height":344,"rx":73,"fill":106,"stroke":98,"style":157},"86.0",[109,371,373],{"x":347,"y":372,"fill":98,"style":349,"textAnchor":350},"105.5","Effort",[69,375],{"x":354,"y":369,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[109,379,380],{"x":358,"y":372,"fill":98,"style":177,"textAnchor":126},"one option",[69,382],{"x":362,"y":369,"width":355,"height":344,"rx":73,"fill":98,"stroke":98,"style":120},[109,384,385],{"x":365,"y":372,"fill":98,"style":177,"textAnchor":126},"explicit code",[69,387],{"x":111,"y":123,"width":343,"height":344,"rx":73,"fill":106,"stroke":98,"style":157},[109,389,391],{"x":347,"y":390,"fill":98,"style":349,"textAnchor":350},"135.5","Custom cache key",[69,393],{"x":354,"y":123,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},[109,395,396],{"x":358,"y":390,"fill":98,"style":177,"textAnchor":126},"cacheKey option",[69,398],{"x":362,"y":123,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},[109,400,401],{"x":365,"y":390,"fill":98,"style":177,"textAnchor":126},"any Request as key",[69,403],{"x":111,"y":404,"width":343,"height":344,"rx":73,"fill":106,"stroke":98,"style":157},"146.0",[109,406,408],{"x":347,"y":407,"fill":98,"style":349,"textAnchor":350},"165.5","Cache transformed responses",[69,410],{"x":354,"y":404,"width":355,"height":344,"rx":73,"fill":411,"stroke":412,"style":413},"#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[109,415,416],{"x":358,"y":407,"fill":98,"style":177,"textAnchor":126},"no",[69,418],{"x":362,"y":404,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},[109,420,421],{"x":365,"y":407,"fill":98,"style":177,"textAnchor":126},"yes",[69,423],{"x":111,"y":424,"width":343,"height":344,"rx":73,"fill":106,"stroke":98,"style":157},"176.0",[109,426,428],{"x":347,"y":427,"fill":98,"style":349,"textAnchor":350},"195.5","Tiered cache \u002F global reach",[69,430],{"x":354,"y":424,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},[109,432,421],{"x":358,"y":427,"fill":98,"style":177,"textAnchor":126},[69,434],{"x":362,"y":424,"width":355,"height":344,"rx":73,"fill":411,"stroke":412,"style":413},[109,436,437],{"x":365,"y":427,"fill":98,"style":177,"textAnchor":126},"local data centre",[69,439],{"x":111,"y":440,"width":343,"height":344,"rx":73,"fill":106,"stroke":98,"style":157},"206.0",[109,442,444],{"x":347,"y":443,"fill":98,"style":349,"textAnchor":350},"225.5","Purge by tag",[69,446],{"x":354,"y":440,"width":355,"height":344,"rx":73,"fill":376,"stroke":376,"style":377},[109,448,449],{"x":358,"y":443,"fill":98,"style":177,"textAnchor":126},"with Cache-Tag",[69,451],{"x":362,"y":440,"width":355,"height":344,"rx":73,"fill":98,"stroke":98,"style":120},[109,453,454],{"x":365,"y":443,"fill":98,"style":177,"textAnchor":126},"purge by URL\u002Fkey",[212,456,458],{"id":457},"step-by-step-resolution","Step-by-Step Resolution",[460,461,463],"h3",{"id":462},"_1-use-cf-options-for-simple-cases","1. Use cf options for simple cases",[465,466,471],"pre",{"className":467,"code":468,"language":469,"meta":470,"style":470},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","export default {\n  async fetch(request) {\n    return fetch(request, {\n      cf: { cacheEverything: true, cacheTtlByStatus: { '200-299': 300, '404': 30, '500-599': 0 } },\n    });\n  },\n};\n\u002F\u002F trade-off: cacheEverything caches HTML regardless of origin headers. Only use\n\u002F\u002F it on routes you know are not personalised, or you will cache private pages.\n","javascript","",[30,472,473,488,505,516,561,567,576,585,592],{"__ignoreMap":470},[474,475,477,481,484],"span",{"class":150,"line":476},1,[474,478,480],{"class":479},"sPARh","export",[474,482,483],{"class":479}," default",[474,485,487],{"class":486},"sQw3B"," {\n",[474,489,491,494,498,501],{"class":150,"line":490},2,[474,492,493],{"class":479},"  async",[474,495,497],{"class":496},"smZ65"," fetch",[474,499,500],{"class":486},"(request) ",[474,502,504],{"class":503},"saISM","{\n",[474,506,508,511,513],{"class":150,"line":507},3,[474,509,510],{"class":479},"    return",[474,512,497],{"class":496},[474,514,515],{"class":503},"(request, {\n",[474,517,519,522,526,529,533,536,539,541,544,546,549,551,554,556,558],{"class":150,"line":518},4,[474,520,521],{"class":503},"      cf: { cacheEverything: ",[474,523,525],{"class":524},"sPXB4","true",[474,527,528],{"class":503},", cacheTtlByStatus: { ",[474,530,532],{"class":531},"sZ8jY","'200-299'",[474,534,535],{"class":503},": ",[474,537,538],{"class":524},"300",[474,540,257],{"class":503},[474,542,543],{"class":531},"'404'",[474,545,535],{"class":503},[474,547,548],{"class":524},"30",[474,550,257],{"class":503},[474,552,553],{"class":531},"'500-599'",[474,555,535],{"class":503},[474,557,73],{"class":524},[474,559,560],{"class":503}," } },\n",[474,562,564],{"class":150,"line":563},5,[474,565,566],{"class":503},"    });\n",[474,568,570,573],{"class":150,"line":569},6,[474,571,572],{"class":503},"  }",[474,574,575],{"class":486},",\n",[474,577,579,582],{"class":150,"line":578},7,[474,580,581],{"class":486},"}",[474,583,584],{"class":503},";\n",[474,586,588],{"class":150,"line":587},8,[474,589,591],{"class":590},"sjfSM","\u002F\u002F trade-off: cacheEverything caches HTML regardless of origin headers. Only use\n",[474,593,595],{"class":150,"line":594},9,[474,596,597],{"class":590},"\u002F\u002F it on routes you know are not personalised, or you will cache private pages.\n",[15,599,600],{},"Expected outcome: HTML or API responses on the route become cacheable at the edge with TTLs you control.",[460,602,604],{"id":603},"_2-use-the-cache-api-with-an-explicit-key-for-custom-logic","2. Use the Cache API with an explicit key for custom logic",[465,606,608],{"className":467,"code":607,"language":469,"meta":470,"style":470},"export default {\n  async fetch(request, env, ctx) {\n    const url = new URL(request.url);\n    url.searchParams.sort();\n    ['utm_source', 'utm_medium', 'gclid', 'fbclid'].forEach((p) => url.searchParams.delete(p));\n    const key = new Request(url.toString(), { method: 'GET' });\n    const cache = caches.default;\n    let res = await cache.match(key);\n    if (res) return res;\n    res = await fetch(request);\n    if (res.ok) {\n      res = new Response(res.body, res);\n      res.headers.set('Cache-Control', 'public, max-age=300');\n      res.headers.delete('Set-Cookie');\n      ctx.waitUntil(cache.put(key, res.clone()));\n    }\n    return res;\n  },\n};\n\u002F\u002F trade-off: deleting Set-Cookie is correct only for responses that are truly\n\u002F\u002F shared. If the origin sets a session cookie here, this route should not be\n\u002F\u002F cached at all.\n",[30,609,610,618,629,649,660,709,738,750,773,787,802,810,826,849,863,886,892,899,906,913,919,925],{"__ignoreMap":470},[474,611,612,614,616],{"class":150,"line":476},[474,613,480],{"class":479},[474,615,483],{"class":479},[474,617,487],{"class":486},[474,619,620,622,624,627],{"class":150,"line":490},[474,621,493],{"class":479},[474,623,497],{"class":496},[474,625,626],{"class":486},"(request, env, ctx) ",[474,628,504],{"class":503},[474,630,631,634,637,640,643,646],{"class":150,"line":507},[474,632,633],{"class":479},"    const",[474,635,636],{"class":524}," url",[474,638,639],{"class":479}," =",[474,641,642],{"class":479}," new",[474,644,645],{"class":496}," URL",[474,647,648],{"class":503},"(request.url);\n",[474,650,651,654,657],{"class":150,"line":518},[474,652,653],{"class":503},"    url.searchParams.",[474,655,656],{"class":496},"sort",[474,658,659],{"class":503},"();\n",[474,661,662,665,668,670,673,675,678,680,683,686,689,692,694,697,700,703,706],{"class":150,"line":563},[474,663,664],{"class":503},"    [",[474,666,667],{"class":531},"'utm_source'",[474,669,257],{"class":503},[474,671,672],{"class":531},"'utm_medium'",[474,674,257],{"class":503},[474,676,677],{"class":531},"'gclid'",[474,679,257],{"class":503},[474,681,682],{"class":531},"'fbclid'",[474,684,685],{"class":503},"].",[474,687,688],{"class":496},"forEach",[474,690,691],{"class":503},"((",[474,693,15],{"class":486},[474,695,696],{"class":503},") ",[474,698,699],{"class":479},"=>",[474,701,702],{"class":503}," url.searchParams.",[474,704,705],{"class":496},"delete",[474,707,708],{"class":503},"(p));\n",[474,710,711,713,716,718,720,723,726,729,732,735],{"class":150,"line":569},[474,712,633],{"class":479},[474,714,715],{"class":524}," key",[474,717,639],{"class":479},[474,719,642],{"class":479},[474,721,722],{"class":496}," Request",[474,724,725],{"class":503},"(url.",[474,727,728],{"class":496},"toString",[474,730,731],{"class":503},"(), { method: ",[474,733,734],{"class":531},"'GET'",[474,736,737],{"class":503}," });\n",[474,739,740,742,745,747],{"class":150,"line":578},[474,741,633],{"class":479},[474,743,744],{"class":524}," cache",[474,746,639],{"class":479},[474,748,749],{"class":503}," caches.default;\n",[474,751,752,755,758,761,764,767,770],{"class":150,"line":587},[474,753,754],{"class":479},"    let",[474,756,757],{"class":503}," res ",[474,759,760],{"class":479},"=",[474,762,763],{"class":479}," await",[474,765,766],{"class":503}," cache.",[474,768,769],{"class":496},"match",[474,771,772],{"class":503},"(key);\n",[474,774,775,778,781,784],{"class":150,"line":594},[474,776,777],{"class":479},"    if",[474,779,780],{"class":503}," (res) ",[474,782,783],{"class":479},"return",[474,785,786],{"class":503}," res;\n",[474,788,790,793,795,797,799],{"class":150,"line":789},10,[474,791,792],{"class":503},"    res ",[474,794,760],{"class":479},[474,796,763],{"class":479},[474,798,497],{"class":496},[474,800,801],{"class":503},"(request);\n",[474,803,805,807],{"class":150,"line":804},11,[474,806,777],{"class":479},[474,808,809],{"class":503}," (res.ok) {\n",[474,811,813,816,818,820,823],{"class":150,"line":812},12,[474,814,815],{"class":503},"      res ",[474,817,760],{"class":479},[474,819,642],{"class":479},[474,821,822],{"class":496}," Response",[474,824,825],{"class":503},"(res.body, res);\n",[474,827,829,832,835,838,841,843,846],{"class":150,"line":828},13,[474,830,831],{"class":503},"      res.headers.",[474,833,834],{"class":496},"set",[474,836,837],{"class":503},"(",[474,839,840],{"class":531},"'Cache-Control'",[474,842,257],{"class":503},[474,844,845],{"class":531},"'public, max-age=300'",[474,847,848],{"class":503},");\n",[474,850,852,854,856,858,861],{"class":150,"line":851},14,[474,853,831],{"class":503},[474,855,705],{"class":496},[474,857,837],{"class":503},[474,859,860],{"class":531},"'Set-Cookie'",[474,862,848],{"class":503},[474,864,866,869,872,875,877,880,883],{"class":150,"line":865},15,[474,867,868],{"class":503},"      ctx.",[474,870,871],{"class":496},"waitUntil",[474,873,874],{"class":503},"(cache.",[474,876,51],{"class":496},[474,878,879],{"class":503},"(key, res.",[474,881,882],{"class":496},"clone",[474,884,885],{"class":503},"()));\n",[474,887,889],{"class":150,"line":888},16,[474,890,891],{"class":503},"    }\n",[474,893,895,897],{"class":150,"line":894},17,[474,896,510],{"class":479},[474,898,786],{"class":503},[474,900,902,904],{"class":150,"line":901},18,[474,903,572],{"class":503},[474,905,575],{"class":486},[474,907,909,911],{"class":150,"line":908},19,[474,910,581],{"class":486},[474,912,584],{"class":503},[474,914,916],{"class":150,"line":915},20,[474,917,918],{"class":590},"\u002F\u002F trade-off: deleting Set-Cookie is correct only for responses that are truly\n",[474,920,922],{"class":150,"line":921},21,[474,923,924],{"class":590},"\u002F\u002F shared. If the origin sets a session cookie here, this route should not be\n",[474,926,928],{"class":150,"line":927},22,[474,929,930],{"class":590},"\u002F\u002F cached at all.\n",[15,932,933],{},"Expected outcome: normalised keys, predictable TTLs and background storage that never delays the response.",[460,935,937],{"id":936},"_3-implement-stale-while-revalidate-in-the-worker","3. Implement stale-while-revalidate in the Worker",[15,939,940,941,252],{},"Store a timestamp header with the cached response; when the entry is older than a soft TTL but younger than a hard TTL, return it immediately and refresh it in ",[30,942,871],{},[465,944,946],{"className":467,"code":945,"language":469,"meta":470,"style":470},"const SOFT = 60_000, HARD = 86_400_000;\nconst cached = await cache.match(key);\nif (cached) {\n  const age = Date.now() - Number(cached.headers.get('x-stored-at') ?? 0);\n  if (age \u003C HARD) {\n    if (age > SOFT) ctx.waitUntil(refresh(key, request, cache));\n    return cached;\n  }\n}\n\u002F\u002F trade-off: hand-rolled SWR is per data centre and can trigger several\n\u002F\u002F concurrent refreshes under load. Add a short lock (e.g. a KV flag) for\n\u002F\u002F expensive origins.\n",[30,947,948,973,990,998,1044,1061,1085,1092,1097,1102,1107,1112],{"__ignoreMap":470},[474,949,950,953,956,958,961,963,966,968,971],{"class":150,"line":476},[474,951,952],{"class":479},"const",[474,954,955],{"class":524}," SOFT",[474,957,639],{"class":479},[474,959,960],{"class":524}," 60_000",[474,962,257],{"class":503},[474,964,965],{"class":524},"HARD",[474,967,639],{"class":479},[474,969,970],{"class":524}," 86_400_000",[474,972,584],{"class":503},[474,974,975,977,980,982,984,986,988],{"class":150,"line":490},[474,976,952],{"class":479},[474,978,979],{"class":524}," cached",[474,981,639],{"class":479},[474,983,763],{"class":479},[474,985,766],{"class":503},[474,987,769],{"class":496},[474,989,772],{"class":503},[474,991,992,995],{"class":150,"line":507},[474,993,994],{"class":479},"if",[474,996,997],{"class":503}," (cached) {\n",[474,999,1000,1003,1006,1008,1011,1014,1017,1020,1023,1026,1029,1031,1034,1036,1039,1042],{"class":150,"line":518},[474,1001,1002],{"class":479},"  const",[474,1004,1005],{"class":524}," age",[474,1007,639],{"class":479},[474,1009,1010],{"class":503}," Date.",[474,1012,1013],{"class":496},"now",[474,1015,1016],{"class":503},"() ",[474,1018,1019],{"class":479},"-",[474,1021,1022],{"class":496}," Number",[474,1024,1025],{"class":503},"(cached.headers.",[474,1027,1028],{"class":496},"get",[474,1030,837],{"class":503},[474,1032,1033],{"class":531},"'x-stored-at'",[474,1035,696],{"class":503},[474,1037,1038],{"class":479},"??",[474,1040,1041],{"class":524}," 0",[474,1043,848],{"class":503},[474,1045,1046,1049,1052,1055,1058],{"class":150,"line":563},[474,1047,1048],{"class":479},"  if",[474,1050,1051],{"class":503}," (age ",[474,1053,1054],{"class":479},"\u003C",[474,1056,1057],{"class":524}," HARD",[474,1059,1060],{"class":503},") {\n",[474,1062,1063,1065,1067,1070,1072,1075,1077,1079,1082],{"class":150,"line":569},[474,1064,777],{"class":479},[474,1066,1051],{"class":503},[474,1068,1069],{"class":479},">",[474,1071,955],{"class":524},[474,1073,1074],{"class":503},") ctx.",[474,1076,871],{"class":496},[474,1078,837],{"class":503},[474,1080,1081],{"class":496},"refresh",[474,1083,1084],{"class":503},"(key, request, cache));\n",[474,1086,1087,1089],{"class":150,"line":578},[474,1088,510],{"class":479},[474,1090,1091],{"class":503}," cached;\n",[474,1093,1094],{"class":150,"line":587},[474,1095,1096],{"class":503},"  }\n",[474,1098,1099],{"class":150,"line":594},[474,1100,1101],{"class":503},"}\n",[474,1103,1104],{"class":150,"line":789},[474,1105,1106],{"class":590},"\u002F\u002F trade-off: hand-rolled SWR is per data centre and can trigger several\n",[474,1108,1109],{"class":150,"line":804},[474,1110,1111],{"class":590},"\u002F\u002F concurrent refreshes under load. Add a short lock (e.g. a KV flag) for\n",[474,1113,1114],{"class":150,"line":812},[474,1115,1116],{"class":590},"\u002F\u002F expensive origins.\n",[460,1118,1120],{"id":1119},"_4-purge-correctly","4. Purge correctly",[15,1122,1123,1124,1126,1127,1129,1130,1133],{},"Entries stored with ",[30,1125,268],{}," can be purged by URL through the Cloudflare API; tag-based purge applies to responses cached by the CDN through ",[30,1128,203],{}," with ",[30,1131,1132],{},"Cache-Tag"," headers. Choose the caching path with your purge strategy in mind.",[15,1135,1136],{},[60,1137,67,1140,67,1143,67,1146,67,1148,67,1151,67,1153,67,1160,67,1167,67,1172,67,1176,67,1181,67,1185,67,1189,67,1193,67,1197,67,1201,67,1205,67],{"viewBox":1138,"width":63,"role":64,"ariaLabel":1139,"style":66},"0 0 760 194","Bar chart of edge cache hit ratio for a product API under different Worker caching approaches.",[69,1141],{"className":1142,"x":73,"y":73,"width":63,"height":63,"fill":74},[72],[76,1144,1145],{},"Hit ratio for an API route by caching approach",[80,1147,1139],{},[69,1149],{"x":102,"y":102,"width":103,"height":1150,"rx":105,"fill":106,"stroke":98,"style":107},"192",[109,1152,1145],{"x":111,"y":112,"fill":98,"style":113},[109,1154,1159],{"x":1155,"y":1156,"fill":98,"style":1157,"textAnchor":1158},"206.9","70.0","font-size:13px","end","No Worker caching (DYNAMIC)",[69,1161],{"x":1162,"y":117,"width":1163,"height":1164,"rx":1165,"fill":411,"stroke":412,"style":1166},"218.9","2.0","19","3","fill-opacity:0.7;stroke-opacity:0.9",[109,1168,1171],{"x":1169,"y":1156,"fill":98,"style":1170},"226.9","font-size:12px;font-weight:600","0%",[109,1173,1175],{"x":1155,"y":1174,"fill":98,"style":1157,"textAnchor":1158},"101.0","Cache API, raw URL keys",[69,1177],{"x":1162,"y":1178,"width":1179,"height":1164,"rx":1165,"fill":376,"stroke":376,"style":1180},"87.0","209.3","fill-opacity:0.55;stroke-opacity:0.9",[109,1182,1184],{"x":1183,"y":1174,"fill":98,"style":1170},"434.1","41%",[109,1186,1188],{"x":1155,"y":1187,"fill":98,"style":1157,"textAnchor":1158},"132.0","Cache API, normalised keys",[69,1190],{"x":1162,"y":1191,"width":1192,"height":1164,"rx":1165,"fill":376,"stroke":376,"style":1180},"118.0","372.6",[109,1194,1196],{"x":1195,"y":1187,"fill":98,"style":1170},"597.4","73%",[109,1198,1200],{"x":1155,"y":1199,"fill":98,"style":1157,"textAnchor":1158},"163.0","cf options + tiered cache",[69,1202],{"x":1162,"y":1203,"width":1204,"height":1164,"rx":1165,"fill":376,"stroke":376,"style":1180},"149.0","449.1",[109,1206,1208],{"x":1207,"y":1199,"fill":98,"style":1170},"674.0","88%",[212,1210,1212],{"id":1211},"verification","Verification",[15,1214,1215,1216,1218,1219,1221,1222,1225],{},"Check ",[30,1217,229],{}," (for ",[30,1220,203],{},"-based caching) or add your own ",[30,1223,1224],{},"x-worker-cache: hit|miss"," header for Cache API paths. Request the same URL with and without tracking parameters and confirm the second is a hit. In RUM, carry the status via Server-Timing and watch TTFB p75 per status. Load-test a cold route to make sure origin traffic stays bounded.",[212,1227,1229],{"id":1228},"worked-example-caching-a-search-api","Worked Example: Caching a Search API",[15,1231,1232,1233,1235],{},"A storefront's search API was called on every keystroke debounce and every results page, all ",[30,1234,233],{},". A Worker normalised the query (lowercased, trimmed, sorted parameters, removed tracking parameters), cached results for 120 seconds with the Cache API and served stale results for up to ten minutes while refreshing in the background. The hit ratio reached 64% at peak, median API latency from the browser fell from 210ms to 35ms on hits, and the origin's search cluster load dropped by over half. Typeahead INP improved too, because results arrived before users typed the next character.",[212,1237,1239],{"id":1238},"common-mistakes","Common Mistakes",[217,1241,1242,1248,1260,1268],{},[220,1243,1244,1247],{},[223,1245,1246],{},"Testing on workers.dev."," The Cache API is a no-op there.",[220,1249,1250,1255,1256,1259],{},[223,1251,1252,1253,252],{},"Awaiting ",[30,1254,268],{}," Use ",[30,1257,1258],{},"ctx.waitUntil"," so storage never delays the response.",[220,1261,1262,67,1265,1267],{},[223,1263,1264],{},"Caching responses with cookies.",[30,1266,51],{}," will refuse, or worse, if you strip headers, you may cache personal data.",[220,1269,1270,1273,1274,1276],{},[223,1271,1272],{},"Forgetting that the Cache API is local."," Global hit rates need tiered caching or ",[30,1275,203],{},"-based caching.",[212,1278,1280],{"id":1279},"edge-cases","Edge Cases",[15,1282,1283,1286,1287,1290],{},[223,1284,1285],{},"Vary headers."," The Cache API matches on URL and respects ",[30,1288,1289],{},"Vary"," in limited ways; avoid relying on it and encode variants in the key URL instead.",[15,1292,1293,1296],{},[223,1294,1295],{},"Large responses."," There are size limits for cached objects; very large files should be cached by the CDN directly or stored in object storage.",[15,1298,1299,1302],{},[223,1300,1301],{},"POST requests."," The Cache API only stores GET; map POST-backed queries to a synthetic GET key when caching them is safe.",[15,1304,1305,1308,1309,1311,1312,1314],{},[223,1306,1307],{},"Cache Reserve and tiered caching."," These platform features increase hit rates for ",[30,1310,203],{},"-based caching; they do not apply to ",[30,1313,310],{}," entries the same way.",[212,1316,1318],{"id":1317},"faq","FAQ",[1320,1321,1324,1328],"details",{"className":1322},[1323],"faq-item",[1325,1326,1327],"summary",{},"Is caches.default the same as the browser Cache API?",[15,1329,1330,1331,257,1333,257,1335,1337,1338,1341],{},"It shares the interface (",[30,1332,769],{},[30,1334,51],{},[30,1336,705],{},") but stores entries in Cloudflare's edge cache for that data centre, not in the browser. Named caches via ",[30,1339,1340],{},"caches.open()"," are separate namespaces in the same edge cache.",[1320,1343,1345,1348],{"className":1344},[1323],[1325,1346,1347],{},"Should I use KV instead of the Cache API?",[15,1349,1350],{},"KV is a globally replicated key-value store with eventual consistency, good for configuration and data you write deliberately. The Cache API is a per-location HTTP cache for responses. For caching origin responses, prefer the cache; use KV for state like locks or small lookups.",[1320,1352,1354,1357],{"className":1353},[1323],[1325,1355,1356],{},"Do cached responses count towards Worker CPU time?",[15,1358,1359,1360,1362],{},"Serving from ",[30,1361,310],{}," still runs the Worker, but cache lookups are I\u002FO rather than CPU and are cheap. For assets that need no logic, avoid routing them through the Worker at all.",[1320,1364,1366,1369],{"className":1365},[1323],[1325,1367,1368],{},"How do I purge entries stored with cache.put?",[15,1370,1371,1372,1129,1374,1376],{},"Purge by URL through the API or dashboard, using the same URL as the key. Tag-based purges target responses cached via ",[30,1373,203],{},[30,1375,1132],{}," headers.",[1320,1378,1380,1383],{"className":1379},[1323],[1325,1381,1382],{},"Can I cache personalised HTML with the Cache API?",[15,1384,1385,1386,252],{},"Only per variant with a safe key, never per user, and only for content that is genuinely shared within the variant — see ",[19,1387,1389],{"href":1388},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fcaching-html-for-logged-in-users-safely\u002F","caching HTML for logged-in users safely",[212,1391,1393],{"id":1392},"related","Related",[217,1395,1396,1403,1410],{},[220,1397,1398,1402],{},[19,1399,1401],{"href":1400},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002F","Stale-while-revalidate implementation"," — the SWR model behind step 3.",[220,1404,1405,1409],{},[19,1406,1408],{"href":1407},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Ftiered-caching-and-origin-shield\u002F","Tiered caching and origin shield"," — raising hit rates beyond one location.",[220,1411,1412,1416],{},[19,1413,1415],{"href":1414},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fcaching-api-responses-at-the-cdn\u002F","Caching API responses at the CDN"," — header-driven API caching.",[1418,1419,1421],"script",{"type":1420},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Use the Cache API in Cloudflare Workers\",\n  \"description\": \"A practical guide to programmatic edge caching in Cloudflare Workers — cache keys, TTLs, background revalidation and the limits that surprise people.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Use cf options for simple cases\",\n      \"text\": \"Expected outcome: HTML or API responses on the route become cacheable at the edge with TTLs you control.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Use the Cache API with an explicit key for custom logic\",\n      \"text\": \"Expected outcome: normalised keys, predictable TTLs and background storage that never delays the response.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Implement stale-while-revalidate in the Worker\",\n      \"text\": \"Store a timestamp header with the cached response; when the entry is older than a soft TTL but younger than a hard TTL, return it immediately and refresh it in waitUntil.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Purge correctly\",\n      \"text\": \"Entries stored with cache.put can be purged by URL through the Cloudflare API; tag-based purge applies to responses cached by the CDN through fetch with Cache-Tag headers.\"\n    }\n  ]\n}\n",[1418,1423,1424],{"type":1420},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Use the Cache API in Cloudflare Workers\",\n  \"description\": \"A practical guide to programmatic edge caching in Cloudflare Workers — cache keys, TTLs, background revalidation and the limits that surprise people.\",\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\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002F\"\n  }\n}\n",[1418,1426,1427],{"type":1420},"\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\": \"Edge Compute & Dynamic Caching\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Using the Cache API in Cloudflare Workers\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002F\"\n    }\n  ]\n}\n",[1429,1430,1431],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}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);}",{"title":470,"searchDepth":490,"depth":490,"links":1433},[1434,1435,1436,1442,1443,1444,1445,1446,1447],{"id":214,"depth":490,"text":215},{"id":277,"depth":490,"text":278},{"id":457,"depth":490,"text":458,"children":1437},[1438,1439,1440,1441],{"id":462,"depth":507,"text":463},{"id":603,"depth":507,"text":604},{"id":936,"depth":507,"text":937},{"id":1119,"depth":507,"text":1120},{"id":1211,"depth":490,"text":1212},{"id":1228,"depth":490,"text":1229},{"id":1238,"depth":490,"text":1239},{"id":1279,"depth":490,"text":1280},{"id":1317,"depth":490,"text":1318},{"id":1392,"depth":490,"text":1393},"A practical guide to programmatic edge caching in Cloudflare Workers — cache keys, TTLs, background revalidation and the limits that surprise people.","md",{"slug":1451,"type":1452,"breadcrumb":1453,"datePublished":1461,"dateModified":1461},"using-the-cache-api-in-cloudflare-workers","article",[1454,1457,1458,1459],{"name":1455,"url":1456},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1460},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers",{"title":5,"description":1465},"Cache responses explicitly from a Cloudflare Worker with caches.default, custom keys and waitUntil, combine it with cf cache options, and avoid common pitfalls.","advanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002Findex","DeQy2G0REXyOMkY0p6YwGvPKxc_Umba4YbvyYDRt1aY",[1469,1473],{"title":1470,"path":1471,"stem":1472},"Personalizing Cached Pages at the Edge","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fpersonalizing-cached-pages-at-the-edge","advanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fpersonalizing-cached-pages-at-the-edge\u002Findex",{"title":1474,"path":1475,"stem":1476},"HTTP Cache-Control Headers Explained","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Findex",1791308075363]