[{"data":1,"prerenderedAt":1470},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy":1463},{"id":4,"title":5,"body":6,"description":1445,"extension":1446,"meta":1447,"navigation":700,"path":1457,"seo":1458,"stem":1461,"__hash__":1462},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002Findex.md","Purging CDN cache by tag on deploy",{"type":7,"value":8,"toc":1428},"minimark",[9,13,33,45,75,185,190,193,271,275,280,291,295,309,313,324,328,334,338,341,526,530,537,631,637,641,644,792,798,875,886,890,897,1070,1078,1082,1095,1100,1104,1107,1266,1282,1285,1374,1384,1388,1419,1424],[10,11,5],"h1",{"id":12},"purging-cdn-cache-by-tag-on-deploy",[14,15,16,17,22,23,27,28,32],"p",{},"This scenario belongs to ",[18,19,21],"a",{"href":20},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002F","Cache Invalidation Patterns"," within ",[18,24,26],{"href":25},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",": your deploy needs to evict exactly the objects the release changed — no more, no less — from a CI job. Tag purge is the primitive for content that changes in place; it is the opposite approach to ",[18,29,31],{"href":30},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Finvalidating-immutable-hashed-assets-safely\u002F","immutable hashed bundles",", which you never purge at all and invalidate by shipping a new filename.",[14,34,35,36,40,41,44],{},"The symptom that brings teams here is one of two opposites. Either a deploy ships but users keep seeing yesterday's content (under-purge), or every deploy hammers the origin with a synchronized miss storm and TTFB blows past ",[37,38,39],"code",{},"200ms"," for thirty seconds (over-purge). Both are tag-design failures, and both are fixable without resorting to ",[37,42,43],{},"purge everything"," on every release.",[14,46,47,48,52,53,56,57,61,62,65,66,69,70,74],{},"Two vocabulary points before the triage. A ",[49,50,51],"strong",{},"surrogate key"," (Fastly) or ",[49,54,55],{},"cache tag"," (Cloudflare) is a label your origin stamps onto a response so the CDN can later evict every object carrying that label in one call — decoupling ",[58,59,60],"em",{},"what you invalidate"," from ",[58,63,64],{},"the URLs it lives at",". And a ",[49,67,68],{},"soft purge"," marks objects stale rather than deleting them, so the edge can serve one last stale response while it revalidates, smoothing the origin refill. These only work if the object exists under a predictable cache key, so tag purge is downstream of getting ",[18,71,73],{"href":72},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002F","CDN edge caching configuration"," right first — a key that fragments by header or query string multiplies the objects a single tag must cover.",[14,76,77],{},[78,79,86,87,86,91,86,95,86,105,86,112,86,119,86,125,86,130,86,136,86,141,86,145,86,149,86,152,86,155,86,159,86,162,86,165,86,171,86,176,86,180,86],"svg",{"xmlns":80,"viewBox":81,"width":82,"role":83,"ariaLabel":84,"style":85},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 280","100%","img","A spectrum showing under-purge, correctly scoped tag purge, and over-purge with their symptoms","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[88,89,90],"title",{},"Purge blast radius spectrum",[92,93,94],"desc",{},"Under-purge leaves stale content, over-purge causes a miss storm; a tag scoped to the change sits between them.",[96,97],"rect",{"x":98,"y":98,"width":99,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"1","758","278","10","none","currentColor","stroke-opacity:0.18",[106,107,111],"text",{"x":108,"y":109,"fill":103,"style":110},"24","36","font-size:17px;font-weight:700","Match the tag to the change",[96,113],{"x":114,"y":115,"width":116,"height":115,"rx":117,"fill":103,"stroke":103,"style":118},"44","74","200","6","stroke-opacity:0.4;fill-opacity:0.06",[96,120],{"x":121,"y":115,"width":116,"height":115,"rx":117,"fill":122,"stroke":123,"style":124},"280","#ffc300","#b8860b","fill-opacity:0.22",[96,126],{"x":127,"y":115,"width":116,"height":115,"rx":117,"fill":128,"stroke":128,"style":129},"516","#0466c8","fill-opacity:0.14",[106,131,135],{"x":132,"y":133,"fill":103,"style":134},"144","100","font-size:13px;font-weight:700;text-anchor:middle","Under-purge",[106,137,140],{"x":132,"y":138,"fill":103,"style":139},"122","font-size:12px;text-anchor:middle","missing tags, 429s",[106,142,144],{"x":132,"y":143,"fill":103,"style":139},"140","stale content ships",[106,146,148],{"x":147,"y":133,"fill":103,"style":134},"380","Scoped tag purge",[106,150,151],{"x":147,"y":138,"fill":103,"style":139},"per-resource keys",[106,153,154],{"x":147,"y":143,"fill":103,"style":139},"evict only changed",[106,156,158],{"x":157,"y":133,"fill":103,"style":134},"616","Over-purge",[106,160,161],{"x":157,"y":138,"fill":103,"style":139},"coarse site tag",[106,163,164],{"x":157,"y":143,"fill":103,"style":139},"origin miss storm",[166,167],"line",{"x1":114,"y1":168,"x2":169,"y2":168,"stroke":103,"style":170},"178","716","stroke-opacity:0.3",[106,172,175],{"x":108,"y":173,"fill":103,"style":174},"208","font-size:12px","Verify: changed URL = MISS, untouched URL = HIT, hit ratio recovers >85%.",[106,177,179],{"x":108,"y":178,"fill":103,"style":174},"236","Compute changed tags from the build manifest; batch and rate-limit calls.",[106,181,184],{"x":108,"y":182,"fill":103,"style":183},"262","font-size:12px;fill-opacity:0.8","Reserve the build-rev key for emergencies, not every deploy.",[186,187,189],"h2",{"id":188},"rapid-diagnosis-is-the-deploy-over-or-under-purging","Rapid Diagnosis: Is the Deploy Over- or Under-Purging?",[14,191,192],{},"Run this checklist the moment a deploy looks wrong:",[194,195,196,214,241,247,257],"ul",{},[197,198,199,202,203,205,206,209,210,213],"li",{},[49,200,201],{},"Check the purge API response in CI logs."," A ",[37,204,116],{},"\u002F",[37,207,208],{},"201"," with the expected key list means the call landed. A ",[37,211,212],{},"429"," means you were rate-limited and the purge silently dropped — classic under-purge.",[197,215,216,86,219,222,223,226,227,205,230,233,234,237,238,240],{},[49,217,218],{},"Read the edge status header on a changed URL right after deploy.",[37,220,221],{},"curl -sI \u003Curl> | grep -i x-cache"," (or ",[37,224,225],{},"cf-cache-status","). It should show ",[37,228,229],{},"MISS",[37,231,232],{},"EXPIRED"," once, then ",[37,235,236],{},"HIT",". A persistent ",[37,239,236],{}," on changed content = under-purge.",[197,242,243,246],{},[49,244,245],{},"Watch origin RPS in the 60s after deploy."," A vertical spike to the entire steady-state cache miss volume = over-purge (you evicted far more than the release touched).",[197,248,249,252,253,256],{},[49,250,251],{},"Diff the tags you purged against the tags the build changed."," If you purged ",[37,254,255],{},"layout-v3"," for a one-product price change, you over-purged the whole site.",[197,258,259,262,263,266,267,270],{},[49,260,261],{},"Target thresholds:"," post-deploy origin RPS within origin headroom, edge hit ratio recovering to ",[37,264,265],{},">85%"," within minutes, propagation ",[37,268,269],{},"\u003C2s",".",[186,272,274],{"id":273},"root-cause-analysis","Root Cause Analysis",[276,277,279],"h3",{"id":278},"_1-over-broad-tags-evict-the-whole-catalog","1. Over-broad tags evict the whole catalog",[14,281,282,283,286,287,290],{},"A single shared tag like ",[37,284,285],{},"site"," or ",[37,288,289],{},"layout"," attached to every response means any deploy that purges it drops the entire edge cache. The blast radius is the union of every URL carrying the tag. This is over-purge by design: the tag granularity is coarser than the change.",[276,292,294],{"id":293},"_2-missing-or-stale-tags-leave-changed-content-cached","2. Missing or stale tags leave changed content cached",[14,296,297,298,205,301,304,305,308],{},"If the origin never stamped a ",[37,299,300],{},"Surrogate-Key",[37,302,303],{},"Cache-Tag"," on a response, no purge can target it. Purging ",[37,306,307],{},"product-42"," evicts nothing because no object carries that key. The deploy reports success and the content stays stale — under-purge from absent tagging.",[276,310,312],{"id":311},"_3-variant-fragmentation-one-logical-page-many-cache-keys","3. Variant fragmentation — one logical page, many cache keys",[14,314,315,316,319,320,323],{},"A page cached under multiple keys (",[37,317,318],{},"Vary: Accept-Encoding",", A\u002FB cookie, device class, query-string permutations) has several edge objects. A purge-by-URL hits one; the others survive. Tag purge solves this only if ",[58,321,322],{},"every"," variant carries the tag.",[276,325,327],{"id":326},"_4-rate-limited-or-unbatched-purge-calls-drop-silently","4. Rate-limited or unbatched purge calls drop silently",[14,329,330,331,333],{},"Firing one purge request per changed URL across hundreds of assets hits the CDN's per-second purge limit. The CDN returns ",[37,332,212],{}," and the excess purges never execute. Under high deploy frequency this looks intermittent and is brutal to debug.",[186,335,337],{"id":336},"step-by-step-resolution","Step-by-Step Resolution",[14,339,340],{},"Fixes are ordered by impact: fix tag granularity first (root cause of over-purge), then guarantee tagging (root cause of under-purge), then batch the API calls. The deploy job then follows one flow — compute the changed keys, batch them under the rate limit, fire the scoped purge, and assert the result before the pipeline goes green.",[78,342,345,346,345,349,345,352,345,371,345,374,345,345,377,345,345,399,345,345,435,345,345,471,345,345,491,345,496,345,502,345,508,345,513,345,516,345,521],{"xmlns":80,"viewBox":343,"width":82,"role":83,"ariaLabel":344,"style":85},"0 0 760 300","Deploy-time purge sequence: git diff maps to cache keys, chunks into 30-tag batches, POSTs a soft purge, then verifies the changed URL misses and the untouched URL still hits, with a dashed emergency branch to the build-rev key used for emergencies only","\n  ",[88,347,348],{},"Deploy-time tag-purge CI sequence",[92,350,351],{},"The CI job diffs the build, maps changed files to surrogate keys, chunks them into batches of at most 30 tags, POSTs a soft purge, and asserts the changed URL returns MISS while an untouched URL stays HIT. A dashed branch reaches the coarse build-rev key, reserved for emergencies only.",[353,354,355,356,345],"defs",{},"\n    ",[357,358,365,366,355],"marker",{"id":359,"viewBox":360,"refX":361,"refY":362,"markerWidth":363,"markerHeight":363,"orient":364},"purgeSeqArrow","0 0 10 10","8.5","5","7","auto-start-reverse","\n      ",[367,368],"path",{"d":369,"fill":102,"stroke":103,"strokeWidth":370},"M0.5,1 L9,5 L0.5,9","1.6",[96,372],{"x":98,"y":98,"width":99,"height":373,"rx":101,"fill":102,"stroke":103,"style":104},"298",[106,375,376],{"x":108,"y":109,"fill":103,"style":110},"Deploy-time purge sequence",[378,379,355,380,355,387,355,390,355,393,355,397,345],"g",{},[96,381],{"x":382,"y":383,"width":384,"height":385,"rx":363,"fill":103,"stroke":103,"style":386},"16","64","128","80","fill-opacity:0.05;stroke-opacity:0.4",[96,388],{"x":389,"y":383,"width":384,"height":385,"rx":363,"fill":103,"stroke":103,"style":386},"166",[96,391],{"x":392,"y":383,"width":384,"height":385,"rx":363,"fill":103,"stroke":103,"style":386},"316",[96,394],{"x":395,"y":383,"width":384,"height":385,"rx":363,"fill":128,"stroke":128,"style":396},"466","fill-opacity:0.12",[96,398],{"x":157,"y":383,"width":384,"height":385,"rx":363,"fill":122,"stroke":123,"style":124},[378,400,355,403,409,355,413,416,355,419,421,355,424,427,355,430,433,345],{"style":401,"textAnchor":402},"font-size:12px;font-weight:700","middle",[404,405],"circle",{"cx":385,"cy":406,"r":407,"fill":103,"style":408},"84","12","fill-opacity:0.9",[106,410,98],{"x":385,"y":411,"fill":412},"88","#ffffff",[404,414],{"cx":415,"cy":406,"r":407,"fill":103,"style":408},"230",[106,417,418],{"x":415,"y":411,"fill":412},"2",[404,420],{"cx":147,"cy":406,"r":407,"fill":103,"style":408},[106,422,423],{"x":147,"y":411,"fill":412},"3",[404,425],{"cx":426,"cy":406,"r":407,"fill":128},"530",[106,428,429],{"x":426,"y":411,"fill":412},"4",[404,431],{"cx":432,"cy":406,"r":407,"fill":123},"680",[106,434,362],{"x":432,"y":411,"fill":412},[378,436,355,437,355,442,355,447,355,450,355,453,355,456,355,459,355,462,355,465,355,468,345],{"textAnchor":402,"fill":103},[106,438,441],{"x":385,"y":439,"style":440},"114","font-size:13px;font-weight:700","git diff",[106,443,446],{"x":385,"y":444,"style":445},"132","font-size:11px","changed files",[106,448,449],{"x":415,"y":439,"style":440},"Map to keys",[106,451,452],{"x":415,"y":444,"style":445},"product-N, section-*",[106,454,455],{"x":147,"y":439,"style":440},"Chunk",[106,457,458],{"x":147,"y":444,"style":445},"≤30-tag batches",[106,460,461],{"x":426,"y":439,"style":440},"POST purge",[106,463,464],{"x":426,"y":444,"style":445},"soft, rate-limited",[106,466,467],{"x":432,"y":439,"style":440},"Verify",[106,469,470],{"x":432,"y":444,"style":445},"MISS vs HIT",[378,472,355,474,355,479,355,483,355,487,345],{"stroke":103,"strokeWidth":370,"markerEnd":473},"url(#purgeSeqArrow)",[166,475],{"x1":476,"y1":477,"x2":478,"y2":477},"146","104","163",[166,480],{"x1":481,"y1":477,"x2":482,"y2":477},"296","313",[166,484],{"x1":485,"y1":477,"x2":486,"y2":477},"446","463",[166,488],{"x1":489,"y1":477,"x2":490,"y2":477},"596","613",[166,492],{"x1":415,"y1":132,"x2":415,"y2":493,"stroke":123,"strokeWidth":370,"strokeDashArray":494,"markerEnd":473,"style":495},"194",[362,429],"color:#b8860b",[106,497,501],{"x":498,"y":499,"fill":123,"style":500},"242","172","font-size:11px;font-style:italic","emergencies only",[96,503],{"x":504,"y":505,"width":506,"height":507,"rx":363,"fill":122,"stroke":123,"style":129},"150","198","160","46",[106,509,512],{"x":415,"y":510,"textAnchor":402,"fill":103,"style":511},"220","font-size:12.5px;font-weight:700","build-rev key",[106,514,515],{"x":415,"y":178,"textAnchor":402,"fill":103,"style":445},"purges the whole release",[166,517],{"x1":382,"y1":518,"x2":519,"y2":518,"stroke":103,"style":520},"264","744","stroke-opacity:0.15",[106,522,525],{"x":108,"y":523,"fill":103,"style":524},"286","font-size:12px;fill-opacity:0.85","Scope the purge to what changed; assert changed=MISS \u002F untouched=HIT before the pipeline goes green.",[276,527,529],{"id":528},"step-1-tag-responses-at-the-right-granularity","Step 1 — Tag responses at the right granularity",[14,531,532,533,536],{},"Stamp keys that match the ",[58,534,535],{},"smallest unit you'll ever invalidate",", plus a few coarse keys for intentional broad purges.",[538,539,544],"pre",{"className":540,"code":541,"language":542,"meta":543,"style":543},"language-nginx shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Per-resource keys + one deliberate coarse key for full-section purges\nlocation ~ ^\u002Fproducts\u002F(\\d+)$ {\n  add_header Surrogate-Key \"product-$1 section-catalog build-$BUILD_REV\";\n  proxy_pass http:\u002F\u002Fbackend;\n}\n# trade-off: build-$BUILD_REV lets you purge an entire release in one call,\n# but if you purge it on EVERY deploy you've reinvented purge-everything.\n# Reserve the build key for emergencies; use product-N keys for normal deploys.\n","nginx","",[37,545,546,554,572,598,607,613,619,625],{"__ignoreMap":543},[547,548,550],"span",{"class":166,"line":549},1,[547,551,553],{"class":552},"sIIH1","# Per-resource keys + one deliberate coarse key for full-section purges\n",[547,555,557,561,564,568],{"class":166,"line":556},2,[547,558,560],{"class":559},"sP5qI","location",[547,562,563],{"class":559}," ~",[547,565,567],{"class":566},"s-_DF"," ^\u002Fproducts\u002F(\\d+)$ ",[547,569,571],{"class":570},"syybb","{\n",[547,573,575,578,581,584,586,589,592,595],{"class":166,"line":574},3,[547,576,577],{"class":559},"  add_header ",[547,579,580],{"class":570},"Surrogate-Key ",[547,582,583],{"class":566},"\"product-$",[547,585,98],{"class":570},[547,587,588],{"class":566}," section-catalog build-$",[547,590,591],{"class":570},"BUILD_REV",[547,593,594],{"class":566},"\"",[547,596,597],{"class":570},";\n",[547,599,601,604],{"class":166,"line":600},4,[547,602,603],{"class":559},"  proxy_pass ",[547,605,606],{"class":570},"http:\u002F\u002Fbackend;\n",[547,608,610],{"class":166,"line":609},5,[547,611,612],{"class":570},"}\n",[547,614,616],{"class":166,"line":615},6,[547,617,618],{"class":552},"# trade-off: build-$BUILD_REV lets you purge an entire release in one call,\n",[547,620,622],{"class":166,"line":621},7,[547,623,624],{"class":552},"# but if you purge it on EVERY deploy you've reinvented purge-everything.\n",[547,626,628],{"class":166,"line":627},8,[547,629,630],{"class":552},"# Reserve the build key for emergencies; use product-N keys for normal deploys.\n",[14,632,633,636],{},[49,634,635],{},"Expected outcome:"," a routine single-product deploy now evicts one object instead of the whole catalog, holding origin RPS flat (over-purge eliminated).",[276,638,640],{"id":639},"step-2-compute-the-affected-tags-from-the-build-then-purge-only-those","Step 2 — Compute the affected tags from the build, then purge only those",[14,642,643],{},"Derive the changed tags from the build manifest or git diff so the purge is scoped to what actually changed.",[538,645,649],{"className":646,"code":647,"language":648,"meta":543,"style":543},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","#!\u002Fusr\u002Fbin\u002Fenv bash\n# ci\u002Fpurge-fastly.sh — purge only the surrogate keys this deploy changed\nset -euo pipefail\n# CHANGED_KEYS is computed from `git diff --name-only` mapped to content ids\nCHANGED_KEYS=\"$1\"   # e.g. \"product-42 product-77 section-catalog\"\n\ncurl -fsS -X POST \"https:\u002F\u002Fapi.fastly.com\u002Fservice\u002F$FASTLY_SERVICE_ID\u002Fpurge\" \\\n  -H \"Fastly-Key: $FASTLY_PURGE_TOKEN\" \\\n  -H \"Fastly-Soft-Purge: 1\" \\\n  -H \"surrogate-key: $CHANGED_KEYS\"\n# trade-off: soft purge serves one stale response per object while it\n# revalidates, smoothing origin load. Do NOT soft-purge a security or\n# pricing fix where a single stale hit is unacceptable — drop the\n# Fastly-Soft-Purge header to force a hard purge there.\n","bash",[37,650,651,656,661,673,678,696,702,729,744,754,768,774,780,786],{"__ignoreMap":543},[547,652,653],{"class":166,"line":549},[547,654,655],{"class":552},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[547,657,658],{"class":166,"line":556},[547,659,660],{"class":552},"# ci\u002Fpurge-fastly.sh — purge only the surrogate keys this deploy changed\n",[547,662,663,667,670],{"class":166,"line":574},[547,664,666],{"class":665},"sf6mN","set",[547,668,669],{"class":665}," -euo",[547,671,672],{"class":566}," pipefail\n",[547,674,675],{"class":166,"line":600},[547,676,677],{"class":552},"# CHANGED_KEYS is computed from `git diff --name-only` mapped to content ids\n",[547,679,680,683,686,688,691,693],{"class":166,"line":609},[547,681,682],{"class":570},"CHANGED_KEYS",[547,684,685],{"class":559},"=",[547,687,594],{"class":566},[547,689,690],{"class":665},"$1",[547,692,594],{"class":566},[547,694,695],{"class":552},"   # e.g. \"product-42 product-77 section-catalog\"\n",[547,697,698],{"class":166,"line":615},[547,699,701],{"emptyLinePlaceholder":700},true,"\n",[547,703,704,708,711,714,717,720,723,726],{"class":166,"line":621},[547,705,707],{"class":706},"seIZK","curl",[547,709,710],{"class":665}," -fsS",[547,712,713],{"class":665}," -X",[547,715,716],{"class":566}," POST",[547,718,719],{"class":566}," \"https:\u002F\u002Fapi.fastly.com\u002Fservice\u002F",[547,721,722],{"class":570},"$FASTLY_SERVICE_ID",[547,724,725],{"class":566},"\u002Fpurge\"",[547,727,728],{"class":559}," \\\n",[547,730,731,734,737,740,742],{"class":166,"line":627},[547,732,733],{"class":665},"  -H",[547,735,736],{"class":566}," \"Fastly-Key: ",[547,738,739],{"class":570},"$FASTLY_PURGE_TOKEN",[547,741,594],{"class":566},[547,743,728],{"class":559},[547,745,747,749,752],{"class":166,"line":746},9,[547,748,733],{"class":665},[547,750,751],{"class":566}," \"Fastly-Soft-Purge: 1\"",[547,753,728],{"class":559},[547,755,757,759,762,765],{"class":166,"line":756},10,[547,758,733],{"class":665},[547,760,761],{"class":566}," \"surrogate-key: ",[547,763,764],{"class":570},"$CHANGED_KEYS",[547,766,767],{"class":566},"\"\n",[547,769,771],{"class":166,"line":770},11,[547,772,773],{"class":552},"# trade-off: soft purge serves one stale response per object while it\n",[547,775,777],{"class":166,"line":776},12,[547,778,779],{"class":552},"# revalidates, smoothing origin load. Do NOT soft-purge a security or\n",[547,781,783],{"class":166,"line":782},13,[547,784,785],{"class":552},"# pricing fix where a single stale hit is unacceptable — drop the\n",[547,787,789],{"class":166,"line":788},14,[547,790,791],{"class":552},"# Fastly-Soft-Purge header to force a hard purge there.\n",[14,793,794,795,797],{},"The Cloudflare Enterprise equivalent purges by ",[37,796,303],{},":",[538,799,801],{"className":646,"code":800,"language":648,"meta":543,"style":543},"# ci\u002Fpurge-cloudflare.sh — tag purge, max 30 tags per request\ncurl -fsS -X POST \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Fzones\u002F$CF_ZONE_ID\u002Fpurge_cache\" \\\n  -H \"Authorization: Bearer $CF_PURGE_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  --data '{\"tags\":[\"product-42\",\"product-77\",\"section-catalog\"]}'\n# trade-off: Cloudflare caps purge_cache at 30 tags per call, so large\n# deploys must chunk the tag list. Don't fall back to {\"purge_everything\":true}\n# when you exceed 30 — batch the tags (Step 3) to keep the blast radius small.\n",[37,802,803,808,829,843,852,860,865,870],{"__ignoreMap":543},[547,804,805],{"class":166,"line":549},[547,806,807],{"class":552},"# ci\u002Fpurge-cloudflare.sh — tag purge, max 30 tags per request\n",[547,809,810,812,814,816,818,821,824,827],{"class":166,"line":556},[547,811,707],{"class":706},[547,813,710],{"class":665},[547,815,713],{"class":665},[547,817,716],{"class":566},[547,819,820],{"class":566}," \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Fzones\u002F",[547,822,823],{"class":570},"$CF_ZONE_ID",[547,825,826],{"class":566},"\u002Fpurge_cache\"",[547,828,728],{"class":559},[547,830,831,833,836,839,841],{"class":166,"line":574},[547,832,733],{"class":665},[547,834,835],{"class":566}," \"Authorization: Bearer ",[547,837,838],{"class":570},"$CF_PURGE_TOKEN",[547,840,594],{"class":566},[547,842,728],{"class":559},[547,844,845,847,850],{"class":166,"line":600},[547,846,733],{"class":665},[547,848,849],{"class":566}," \"Content-Type: application\u002Fjson\"",[547,851,728],{"class":559},[547,853,854,857],{"class":166,"line":609},[547,855,856],{"class":665},"  --data",[547,858,859],{"class":566}," '{\"tags\":[\"product-42\",\"product-77\",\"section-catalog\"]}'\n",[547,861,862],{"class":166,"line":615},[547,863,864],{"class":552},"# trade-off: Cloudflare caps purge_cache at 30 tags per call, so large\n",[547,866,867],{"class":166,"line":621},[547,868,869],{"class":552},"# deploys must chunk the tag list. Don't fall back to {\"purge_everything\":true}\n",[547,871,872],{"class":166,"line":627},[547,873,874],{"class":552},"# when you exceed 30 — batch the tags (Step 3) to keep the blast radius small.\n",[14,876,877,879,880,882,883,885],{},[49,878,635],{}," the deploy evicts exactly the changed content; previously stale pages now show ",[37,881,229],{}," then ",[37,884,236],{},", closing the under-purge gap.",[276,887,889],{"id":888},"step-3-batch-and-rate-limit-the-purge-calls","Step 3 — Batch and rate-limit the purge calls",[14,891,892,893,896],{},"Group keys into a single request (Fastly accepts many keys in one ",[37,894,895],{},"surrogate-key"," header; Cloudflare allows 30 tags per call) and chunk anything larger.",[538,898,900],{"className":646,"code":899,"language":648,"meta":543,"style":543},"# Chunk a long tag list into 30-tag batches for Cloudflare\nprintf '%s\\n' $CHANGED_KEYS | xargs -n 30 | while read -r batch; do\n  tags=$(printf '\"%s\",' $batch | sed 's\u002F,$\u002F\u002F')\n  curl -fsS -X POST \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Fzones\u002F$CF_ZONE_ID\u002Fpurge_cache\" \\\n    -H \"Authorization: Bearer $CF_PURGE_TOKEN\" -H \"Content-Type: application\u002Fjson\" \\\n    --data \"{\\\"tags\\\":[$tags]}\"\n  sleep 1\ndone\n# trade-off: the 1s pause keeps you under the purge rate limit but adds\n# latency to large deploys. Drop it for small tag sets; keep it when a\n# release can touch hundreds of tags, or the 429s return.\n",[37,901,902,907,951,980,999,1017,1042,1050,1055,1060,1065],{"__ignoreMap":543},[547,903,904],{"class":166,"line":549},[547,905,906],{"class":552},"# Chunk a long tag list into 30-tag batches for Cloudflare\n",[547,908,909,912,915,918,921,924,927,930,933,936,939,942,945,948],{"class":166,"line":556},[547,910,911],{"class":665},"printf",[547,913,914],{"class":566}," '%s\\n'",[547,916,917],{"class":570}," $CHANGED_KEYS ",[547,919,920],{"class":559},"|",[547,922,923],{"class":706}," xargs",[547,925,926],{"class":665}," -n",[547,928,929],{"class":665}," 30",[547,931,932],{"class":559}," |",[547,934,935],{"class":559}," while",[547,937,938],{"class":665}," read",[547,940,941],{"class":665}," -r",[547,943,944],{"class":566}," batch",[547,946,947],{"class":570},"; ",[547,949,950],{"class":559},"do\n",[547,952,953,956,958,961,963,966,969,971,974,977],{"class":166,"line":574},[547,954,955],{"class":570},"  tags",[547,957,685],{"class":559},[547,959,960],{"class":570},"$(",[547,962,911],{"class":665},[547,964,965],{"class":566}," '\"%s\",'",[547,967,968],{"class":570}," $batch ",[547,970,920],{"class":559},[547,972,973],{"class":706}," sed",[547,975,976],{"class":566}," 's\u002F,$\u002F\u002F'",[547,978,979],{"class":570},")\n",[547,981,982,985,987,989,991,993,995,997],{"class":166,"line":600},[547,983,984],{"class":706},"  curl",[547,986,710],{"class":665},[547,988,713],{"class":665},[547,990,716],{"class":566},[547,992,820],{"class":566},[547,994,823],{"class":570},[547,996,826],{"class":566},[547,998,728],{"class":559},[547,1000,1001,1004,1006,1008,1010,1013,1015],{"class":166,"line":609},[547,1002,1003],{"class":665},"    -H",[547,1005,835],{"class":566},[547,1007,838],{"class":570},[547,1009,594],{"class":566},[547,1011,1012],{"class":665}," -H",[547,1014,849],{"class":566},[547,1016,728],{"class":559},[547,1018,1019,1022,1025,1028,1031,1033,1036,1039],{"class":166,"line":615},[547,1020,1021],{"class":665},"    --data",[547,1023,1024],{"class":566}," \"{",[547,1026,1027],{"class":559},"\\\"",[547,1029,1030],{"class":566},"tags",[547,1032,1027],{"class":559},[547,1034,1035],{"class":566},":[",[547,1037,1038],{"class":570},"$tags",[547,1040,1041],{"class":566},"]}\"\n",[547,1043,1044,1047],{"class":166,"line":621},[547,1045,1046],{"class":706},"  sleep",[547,1048,1049],{"class":665}," 1\n",[547,1051,1052],{"class":166,"line":627},[547,1053,1054],{"class":559},"done\n",[547,1056,1057],{"class":166,"line":746},[547,1058,1059],{"class":552},"# trade-off: the 1s pause keeps you under the purge rate limit but adds\n",[547,1061,1062],{"class":166,"line":756},[547,1063,1064],{"class":552},"# latency to large deploys. Drop it for small tag sets; keep it when a\n",[547,1066,1067],{"class":166,"line":770},[547,1068,1069],{"class":552},"# release can touch hundreds of tags, or the 429s return.\n",[14,1071,1072,1074,1075,1077],{},[49,1073,635],{}," no more silent ",[37,1076,212],{}," drops; every intended key is evicted even on large deploys (under-purge from rate limiting eliminated).",[276,1079,1081],{"id":1080},"step-4-guarantee-every-variant-carries-the-tag","Step 4 — Guarantee every variant carries the tag",[14,1083,1084,1085,1087,1088,1091,1092,270],{},"Apply the ",[37,1086,300],{}," before any ",[37,1089,1090],{},"Vary","-splitting or A\u002FB logic so all variants of a page inherit the same tag, making tag purge cover what URL purge cannot. For CloudFront, which has no native tagging, fall back to path-pattern invalidations scoped to the changed prefixes rather than ",[37,1093,1094],{},"\u002F*",[14,1096,1097,1099],{},[49,1098,635],{}," every cached variant of a changed page is evicted by a single tag purge; no surviving stale variant.",[186,1101,1103],{"id":1102},"verification","Verification",[14,1105,1106],{},"Confirm the deploy purged correctly with a before\u002Fafter check wired into CI:",[538,1108,1110],{"className":646,"code":1109,"language":648,"meta":543,"style":543},"# Assert a changed URL is fresh, an unchanged URL is still warm\nchanged=\"https:\u002F\u002Fyour-domain.com\u002Fproducts\u002F42\"\nuntouched=\"https:\u002F\u002Fyour-domain.com\u002Fabout\u002F\"\n\ncurl -sI \"$changed\"  | grep -i x-cache | grep -qiE 'miss|expired' \\\n  || { echo \"FAIL: changed page still cached (under-purge)\"; exit 1; }\ncurl -sI \"$untouched\" | grep -i x-cache | grep -qiE 'hit' \\\n  || { echo \"FAIL: unrelated page evicted (over-purge)\"; exit 1; }\n# trade-off: checks one URL per class. Sample one per tag, not all URLs —\n# verifying hundreds in CI becomes its own load test.\n",[37,1111,1112,1117,1127,1137,1141,1180,1205,1237,1256,1261],{"__ignoreMap":543},[547,1113,1114],{"class":166,"line":549},[547,1115,1116],{"class":552},"# Assert a changed URL is fresh, an unchanged URL is still warm\n",[547,1118,1119,1122,1124],{"class":166,"line":556},[547,1120,1121],{"class":570},"changed",[547,1123,685],{"class":559},[547,1125,1126],{"class":566},"\"https:\u002F\u002Fyour-domain.com\u002Fproducts\u002F42\"\n",[547,1128,1129,1132,1134],{"class":166,"line":574},[547,1130,1131],{"class":570},"untouched",[547,1133,685],{"class":559},[547,1135,1136],{"class":566},"\"https:\u002F\u002Fyour-domain.com\u002Fabout\u002F\"\n",[547,1138,1139],{"class":166,"line":600},[547,1140,701],{"emptyLinePlaceholder":700},[547,1142,1143,1145,1148,1151,1154,1156,1159,1162,1165,1168,1170,1172,1175,1178],{"class":166,"line":609},[547,1144,707],{"class":706},[547,1146,1147],{"class":665}," -sI",[547,1149,1150],{"class":566}," \"",[547,1152,1153],{"class":570},"$changed",[547,1155,594],{"class":566},[547,1157,1158],{"class":559},"  |",[547,1160,1161],{"class":706}," grep",[547,1163,1164],{"class":665}," -i",[547,1166,1167],{"class":566}," x-cache",[547,1169,932],{"class":559},[547,1171,1161],{"class":706},[547,1173,1174],{"class":665}," -qiE",[547,1176,1177],{"class":566}," 'miss|expired'",[547,1179,728],{"class":559},[547,1181,1182,1185,1188,1191,1194,1196,1199,1202],{"class":166,"line":615},[547,1183,1184],{"class":559},"  ||",[547,1186,1187],{"class":570}," { ",[547,1189,1190],{"class":665},"echo",[547,1192,1193],{"class":566}," \"FAIL: changed page still cached (under-purge)\"",[547,1195,947],{"class":570},[547,1197,1198],{"class":665},"exit",[547,1200,1201],{"class":665}," 1",[547,1203,1204],{"class":570},"; }\n",[547,1206,1207,1209,1211,1213,1216,1218,1220,1222,1224,1226,1228,1230,1232,1235],{"class":166,"line":621},[547,1208,707],{"class":706},[547,1210,1147],{"class":665},[547,1212,1150],{"class":566},[547,1214,1215],{"class":570},"$untouched",[547,1217,594],{"class":566},[547,1219,932],{"class":559},[547,1221,1161],{"class":706},[547,1223,1164],{"class":665},[547,1225,1167],{"class":566},[547,1227,932],{"class":559},[547,1229,1161],{"class":706},[547,1231,1174],{"class":665},[547,1233,1234],{"class":566}," 'hit'",[547,1236,728],{"class":559},[547,1238,1239,1241,1243,1245,1248,1250,1252,1254],{"class":166,"line":627},[547,1240,1184],{"class":559},[547,1242,1187],{"class":570},[547,1244,1190],{"class":665},[547,1246,1247],{"class":566}," \"FAIL: unrelated page evicted (over-purge)\"",[547,1249,947],{"class":570},[547,1251,1198],{"class":665},[547,1253,1201],{"class":665},[547,1255,1204],{"class":570},[547,1257,1258],{"class":166,"line":746},[547,1259,1260],{"class":552},"# trade-off: checks one URL per class. Sample one per tag, not all URLs —\n",[547,1262,1263],{"class":166,"line":756},[547,1264,1265],{"class":552},"# verifying hundreds in CI becomes its own load test.\n",[14,1267,1268,1269,1272,1273,1276,1277,1281],{},"The combined assertion is the tell: the changed URL must miss, the untouched URL must still hit. If both pass, the purge was correctly scoped. In production, confirm via CDN analytics that the post-deploy edge hit ratio dips only slightly and recovers above ",[37,1270,1271],{},"85%"," within minutes, and that origin RPS never exceeded its ",[37,1274,1275],{},"TTFB ≤ 200ms"," headroom. Tie soft purge to a stale window per ",[18,1278,1280],{"href":1279},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002F","Stale-While-Revalidate Implementation"," to keep the refill smooth.",[14,1283,1284],{},"The before\u002Fafter picture on a single-product deploy makes the win concrete:",[1286,1287,1288,1307],"table",{},[1289,1290,1291],"thead",{},[1292,1293,1294,1298,1304],"tr",{},[1295,1296,1297],"th",{},"Signal",[1295,1299,1300,1301,1303],{},"Coarse ",[37,1302,285],{}," purge (before)",[1295,1305,1306],{},"Scoped key purge (after)",[1308,1309,1310,1322,1333,1350,1364],"tbody",{},[1292,1311,1312,1316,1319],{},[1313,1314,1315],"td",{},"Objects evicted",[1313,1317,1318],{},"entire catalog (~40k)",[1313,1320,1321],{},"1 product + section",[1292,1323,1324,1327,1330],{},[1313,1325,1326],{},"Origin RPS spike",[1313,1328,1329],{},"full steady-state miss volume",[1313,1331,1332],{},"flat, within headroom",[1292,1334,1335,1338,1344],{},[1313,1336,1337],{},"Edge hit-ratio floor",[1313,1339,1340,1341],{},"drops below ",[37,1342,1343],{},"40%",[1313,1345,1346,1347,1349],{},"dips a few points, recovers ",[37,1348,265],{}," in minutes",[1292,1351,1352,1355,1361],{},[1313,1353,1354],{},"Post-deploy p75 TTFB",[1313,1356,1357,1360],{},[37,1358,1359],{},">200ms"," for ~30s",[1313,1362,1363],{},"unchanged",[1292,1365,1366,1369,1372],{},[1313,1367,1368],{},"Stale content risk",[1313,1370,1371],{},"none, but self-inflicted load",[1313,1373,102],{},[14,1375,1376,1377,1379,1380,270],{},"Wire the two ",[37,1378,707],{}," assertions above into the deploy job so a mis-scoped purge fails the pipeline instead of reaching users. For content the CDN can no longer reach — anything already cached in the browser or a Service Worker — a tag purge does nothing; pair the deploy with a client-tier cache-busting strategy from ",[18,1381,1383],{"href":1382},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F","Service Worker Caching Strategies",[186,1385,1387],{"id":1386},"related","Related",[194,1389,1390,1395,1401,1407,1414],{},[197,1391,1392,1394],{},[18,1393,21],{"href":20}," — the four purge primitives and when each applies.",[197,1396,1397,1400],{},[18,1398,1399],{"href":72},"CDN Edge Caching Configuration"," — aligning cache keys so tags resolve to the objects you expect.",[197,1402,1403,1406],{},[18,1404,1405],{"href":30},"Invalidating immutable hashed assets safely"," — why hashed bundles need omission, not tag purge.",[197,1408,1409,1413],{},[18,1410,1412],{"href":1411},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002F","Setting up immutable cache headers for hashed assets"," — the header config that makes invalidation-by-omission work.",[197,1415,1416,1418],{},[18,1417,1383],{"href":1382}," — invalidating the client tier a CDN purge can't reach.",[1420,1421,1423],"script",{"type":1422},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"TechArticle\",\n      \"headline\": \"Purging CDN cache by tag on deploy\",\n      \"description\": \"Fix over- and under-purging by evicting only the surrogate keys a release changed, using Fastly and Cloudflare purge APIs in CI.\",\n      \"datePublished\": \"2026-06-18\",\n      \"dateModified\": \"2026-06-18\",\n      \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n      \"mainEntityOfPage\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F\"\n    },\n    {\n      \"@type\": \"HowTo\",\n      \"name\": \"Purge only the affected CDN assets by cache tag during a deploy\",\n      \"step\": [\n        { \"@type\": \"HowToStep\", \"name\": \"Tag responses at the right granularity\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F#step-1--tag-responses-at-the-right-granularity\" },\n        { \"@type\": \"HowToStep\", \"name\": \"Compute affected tags and purge only those\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F#step-2--compute-the-affected-tags-from-the-build-then-purge-only-those\" },\n        { \"@type\": \"HowToStep\", \"name\": \"Batch and rate-limit the purge calls\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F#step-3--batch-and-rate-limit-the-purge-calls\" },\n        { \"@type\": \"HowToStep\", \"name\": \"Guarantee every variant carries the tag\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F#step-4--guarantee-every-variant-carries-the-tag\" }\n      ]\n    },\n    {\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\": \"Cache Invalidation Patterns\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Purging CDN cache by tag on deploy\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F\" }\n      ]\n    }\n  ]\n}\n",[1425,1426,1427],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}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 .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}",{"title":543,"searchDepth":556,"depth":556,"links":1429},[1430,1431,1437,1443,1444],{"id":188,"depth":556,"text":189},{"id":273,"depth":556,"text":274,"children":1432},[1433,1434,1435,1436],{"id":278,"depth":574,"text":279},{"id":293,"depth":574,"text":294},{"id":311,"depth":574,"text":312},{"id":326,"depth":574,"text":327},{"id":336,"depth":556,"text":337,"children":1438},[1439,1440,1441,1442],{"id":528,"depth":574,"text":529},{"id":639,"depth":574,"text":640},{"id":888,"depth":574,"text":889},{"id":1080,"depth":574,"text":1081},{"id":1102,"depth":556,"text":1103},{"id":1386,"depth":556,"text":1387},"A deploy-time runbook for evicting only the assets a release touched, using Fastly and Cloudflare surrogate-key purge APIs.","md",{"slug":12,"type":1448,"breadcrumb":1449,"datePublished":1456,"dateModified":1456},"article",[1450,1452,1453,1454],{"name":1451,"url":205},"Home",{"name":26,"url":25},{"name":21,"url":20},{"name":5,"url":1455},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F","2026-06-18","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy",{"title":1459,"description":1460},"Purging CDN Cache by Tag on Deploy","Automatically purge only the assets affected by a deploy using cache tags and surrogate keys. Fix over-purging stampedes and under-purging stale content in CI.","advanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002Findex","jSX77cFfGh8afO12_cr0_2WGsqxqWYCvA0sLj0JL6AM",[1464,1467],{"title":1405,"path":1465,"stem":1466},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Finvalidating-immutable-hashed-assets-safely","advanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Finvalidating-immutable-hashed-assets-safely\u002Findex",{"title":1399,"path":1468,"stem":1469},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration","advanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Findex",1784398534278]