[{"data":1,"prerenderedAt":1058},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing":1049},{"id":4,"title":5,"body":6,"description":1029,"extension":1030,"meta":1031,"navigation":1043,"path":1044,"seo":1045,"stem":1047,"__hash__":1048},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing\u002Findex.md","Heuristic Freshness When Cache-Control Is Missing",{"type":7,"value":8,"toc":1012},"minimark",[9,14,42,51,169,174,237,241,253,259,265,271,384,388,393,524,528,654,657,661,664,668,671,780,784,790,794,806,810,816,820,851,855,861,867,873,879,883,898,918,927,936,949,958,970,974,997,1002,1005,1008],[10,11,13],"h1",{"id":12},"heuristic-freshness-what-happens-when-cache-control-is-missing","Heuristic Freshness: What Happens When Cache-Control Is Missing",[15,16,17,18,23,24,28,29,33,34,37,38,41],"p",{},"This guide explains a hidden behaviour behind many caching bugs, within ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002F","HTTP Cache-Control Headers Explained"," and ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". When a response has no ",[30,31,32],"code",{},"Cache-Control"," max-age and no ",[30,35,36],{},"Expires",", HTTP caches are allowed to pick a freshness lifetime themselves — \"heuristic freshness\". The common heuristic, suggested by the HTTP specification and used by browsers, is 10% of the time since ",[30,39,40],{},"Last-Modified",". A file last modified 30 days ago gets roughly three days of freshness, without anyone having decided that.",[15,43,44,45,47,48,50],{},"That sounds harmless until a script or stylesheet with a stable filename is updated: some visitors keep the old version for days, mixing old JavaScript with new HTML. Or an API response with a ",[30,46,40],{}," header from long ago is reused for hours. The reverse also happens — responses that should be cached are not, because no ",[30,49,40],{}," is present. Heuristic caching is unpredictable by design, and the cure is to set explicit policies everywhere.",[15,52,53],{},[54,55,61,62,61,69,61,73,61,76,61,85,61,91,61,96,61,104,61,111,61,115,61,123,61,128,61,135,61,141,61,145,61,149,61,152,61,156,61,160,61,164,61],"svg",{"viewBox":56,"width":57,"role":58,"ariaLabel":59,"style":60},"0 0 760 204","100%","img","Timeline showing a file last modified 30 days before it was fetched receiving about three days of heuristic freshness.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[63,64],"rect",{"className":65,"x":67,"y":67,"width":57,"height":57,"fill":68},[66],"svg-canvas","0","#ffffff",[70,71,72],"title",{},"Heuristic lifetime for a file last modified 30 days ago",[74,75,59],"desc",{},[63,77],{"x":78,"y":78,"width":79,"height":80,"rx":81,"fill":82,"stroke":83,"style":84},"1","758","202","10","none","currentColor","stroke-opacity:0.18",[86,87,72],"text",{"x":88,"y":89,"fill":83,"style":90},"28.0","34.0","font-size:16px;font-weight:700",[86,92,95],{"x":88,"y":93,"fill":83,"style":94},"74.0","font-size:12.5px;font-weight:700","File age",[63,97],{"x":98,"y":99,"width":100,"height":101,"rx":102,"fill":83,"stroke":83,"style":103},"90.4","56.0","560.0","26.0","3","fill-opacity:0.06;stroke-opacity:0.4",[86,105,110],{"x":106,"y":107,"fill":83,"style":108,"textAnchor":109},"370.4","73.5","font-size:11.5px","middle","since Last-Modified",[86,112,114],{"x":88,"y":113,"fill":83,"style":94},"112.0","Cache",[63,116],{"x":117,"y":118,"width":119,"height":101,"rx":102,"fill":120,"stroke":121,"style":122},"651.9","94.0","54.6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[86,124,127],{"x":125,"y":126,"fill":83,"style":108,"textAnchor":109},"679.2","111.5","~3 days",[129,130],"line",{"x1":131,"y1":132,"x2":133,"y2":132,"stroke":83,"style":134},"89.7","134.0","726.0","stroke-opacity:0.4",[86,136,140],{"x":137,"y":138,"fill":139,"style":108,"textAnchor":109},"95.7","151.0","#51617a","0d",[86,142,144],{"x":143,"y":138,"fill":139,"style":108,"textAnchor":109},"183.3","5d",[86,146,148],{"x":147,"y":138,"fill":139,"style":108,"textAnchor":109},"276.8","10d",[86,150,151],{"x":106,"y":138,"fill":139,"style":108,"textAnchor":109},"15d",[86,153,155],{"x":154,"y":138,"fill":139,"style":108,"textAnchor":109},"464.0","20d",[86,157,159],{"x":158,"y":138,"fill":139,"style":108,"textAnchor":109},"557.6","25d",[86,161,163],{"x":162,"y":138,"fill":139,"style":108,"textAnchor":109},"651.1","30d",[86,165,168],{"x":88,"y":166,"fill":139,"style":167},"182.0","font-size:12.5px","Ten percent of the 30 days since modification becomes a freshness lifetime nobody chose.",[170,171,173],"h2",{"id":172},"rapid-diagnosis","Rapid Diagnosis",[175,176,177,206,222,231],"ul",{},[178,179,180,184,185,187,188,191,192,191,195,198,199,202,203,205],"li",{},[181,182,183],"strong",{},"Scan responses for missing freshness:"," for each resource type, check whether ",[30,186,32],{}," (with ",[30,189,190],{},"max-age",", ",[30,193,194],{},"s-maxage",[30,196,197],{},"no-cache"," or ",[30,200,201],{},"no-store",") or ",[30,204,36],{}," is present.",[178,207,208,211,212,191,215,191,218,221],{},[181,209,210],{},"Look for stable-name assets"," (",[30,213,214],{},"\u002Fjs\u002Fapp.js",[30,216,217],{},"\u002Fcss\u002Fmain.css",[30,219,220],{},"\u002Fconfig.json",") served without explicit headers.",[178,223,224,227,228,230],{},[181,225,226],{},"Check DevTools."," Resources served \"from disk cache\" with no ",[30,229,32],{}," header are being cached heuristically.",[178,232,233,236],{},[181,234,235],{},"Check CDN defaults."," CDNs apply their own default TTLs to responses without headers, which may differ from browsers' heuristics.",[170,238,240],{"id":239},"root-cause-analysis","Root Cause Analysis",[15,242,243,246,247,249,250,252],{},[181,244,245],{},"1. Origins that send no caching headers."," Default web server configurations often send ",[30,248,40],{}," but no ",[30,251,32],{},".",[15,254,255,258],{},[181,256,257],{},"2. Stable filenames."," Without content hashes in names, heuristic caching keeps old versions after updates.",[15,260,261,264],{},[181,262,263],{},"3. Different caches, different heuristics."," Browsers, CDNs and proxies each choose their own lifetimes, so behaviour differs by path through the network.",[15,266,267,270],{},[181,268,269],{},"4. Old Last-Modified dates."," Files deployed with preserved old dates get long heuristic lifetimes.",[15,272,273],{},[54,274,61,277,61,280,61,283,61,285,61,288,61,290,61,294,61,299,61,303,61,307,61,310,61,314,61,318,61,322,61,324,61,328,61,330,61,333,61,336,61,340,61,342,61,345,61,347,61,350,61,353,61,357,61,359,61,362,61,364,61,367,61,370,61,374,61,376,61,379,61,381,61],{"viewBox":275,"width":57,"role":58,"ariaLabel":276,"style":60},"0 0 760 228","How browsers, CDNs and proxies behave for responses with no explicit freshness information.",[63,278],{"className":279,"x":67,"y":67,"width":57,"height":57,"fill":68},[66],[70,281,282],{},"Who chooses the lifetime when headers are missing",[74,284,276],{},[63,286],{"x":78,"y":78,"width":79,"height":287,"rx":81,"fill":82,"stroke":83,"style":84},"226",[86,289,282],{"x":88,"y":89,"fill":83,"style":90},[63,291],{"x":88,"y":99,"width":292,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},"113.6","30.0",[86,295,114],{"x":296,"y":297,"fill":83,"style":94,"textAnchor":298},"38.0","75.5","start",[63,300],{"x":301,"y":99,"width":302,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},"141.6","295.2",[86,304,306],{"x":305,"y":297,"fill":83,"style":94,"textAnchor":109},"289.2","Behaviour without Cache-Control",[63,308],{"x":309,"y":99,"width":302,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},"436.8",[86,311,313],{"x":312,"y":297,"fill":83,"style":94,"textAnchor":109},"584.4","Risk",[63,315],{"x":88,"y":316,"width":292,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},"86.0","stroke-opacity:0.35",[86,319,321],{"x":296,"y":320,"fill":83,"style":94,"textAnchor":298},"105.5","Browser",[63,323],{"x":301,"y":316,"width":302,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},[86,325,327],{"x":305,"y":320,"fill":83,"style":326,"textAnchor":109},"font-size:12px","~10% of age since Last-Modified",[63,329],{"x":309,"y":316,"width":302,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},[86,331,332],{"x":312,"y":320,"fill":83,"style":326,"textAnchor":109},"stale scripts and styles",[63,334],{"x":88,"y":335,"width":292,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},"116.0",[86,337,339],{"x":296,"y":338,"fill":83,"style":94,"textAnchor":298},"135.5","CDN",[63,341],{"x":301,"y":335,"width":302,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},[86,343,344],{"x":305,"y":338,"fill":83,"style":326,"textAnchor":109},"vendor default TTL (varies)",[63,346],{"x":309,"y":335,"width":302,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},[86,348,349],{"x":312,"y":338,"fill":83,"style":326,"textAnchor":109},"inconsistent edge behaviour",[63,351],{"x":88,"y":352,"width":292,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},"146.0",[86,354,356],{"x":296,"y":355,"fill":83,"style":94,"textAnchor":298},"165.5","Corporate proxy",[63,358],{"x":301,"y":352,"width":302,"height":293,"rx":67,"fill":83,"stroke":83,"style":103},[86,360,361],{"x":305,"y":355,"fill":83,"style":326,"textAnchor":109},"its own heuristic",[63,363],{"x":309,"y":352,"width":302,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},[86,365,366],{"x":312,"y":355,"fill":83,"style":326,"textAnchor":109},"hard-to-debug staleness",[63,368],{"x":88,"y":369,"width":292,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},"176.0",[86,371,373],{"x":296,"y":372,"fill":83,"style":94,"textAnchor":298},"195.5","All",[63,375],{"x":301,"y":369,"width":302,"height":293,"rx":67,"fill":120,"stroke":121,"style":122},[86,377,378],{"x":305,"y":372,"fill":83,"style":326,"textAnchor":109},"nothing if no Last-Modified",[63,380],{"x":309,"y":369,"width":302,"height":293,"rx":67,"fill":82,"stroke":83,"style":317},[86,382,383],{"x":312,"y":372,"fill":83,"style":326,"textAnchor":109},"missed caching opportunities",[170,385,387],{"id":386},"step-by-step-resolution","Step-by-Step Resolution",[389,390,392],"h3",{"id":391},"_1-audit-freshness-headers-by-resource-type","1. Audit freshness headers by resource type",[394,395,400],"pre",{"className":396,"code":397,"language":398,"meta":399,"style":399},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","for u in \u002F \u002Fcss\u002Fmain.css \u002Fjs\u002Fapp.js \u002Fapi\u002Fconfig \u002Fimages\u002Flogo.png; do\n  printf '%-20s ' \"$u\"\n  curl -sI \"https:\u002F\u002Fwww.example.com$u\" | grep -iE '^(cache-control|expires|last-modified):' | tr '\\r\\n' ' '\n  echo\ndone\n# trade-off: a handful of URLs is a sample. Use CDN logs to list response\n# headers by content type across all traffic for a complete picture.\n","bash","",[30,401,402,439,458,499,505,511,518],{"__ignoreMap":399},[403,404,406,410,414,417,421,424,427,430,433,436],"span",{"class":129,"line":405},1,[403,407,409],{"class":408},"sPARh","for",[403,411,413],{"class":412},"saISM"," u ",[403,415,416],{"class":408},"in",[403,418,420],{"class":419},"sZ8jY"," \u002F",[403,422,423],{"class":419}," \u002Fcss\u002Fmain.css",[403,425,426],{"class":419}," \u002Fjs\u002Fapp.js",[403,428,429],{"class":419}," \u002Fapi\u002Fconfig",[403,431,432],{"class":419}," \u002Fimages\u002Flogo.png",[403,434,435],{"class":412},"; ",[403,437,438],{"class":408},"do\n",[403,440,442,446,449,452,455],{"class":129,"line":441},2,[403,443,445],{"class":444},"sPXB4","  printf",[403,447,448],{"class":419}," '%-20s '",[403,450,451],{"class":419}," \"",[403,453,454],{"class":412},"$u",[403,456,457],{"class":419},"\"\n",[403,459,461,465,468,471,473,476,479,482,485,488,490,493,496],{"class":129,"line":460},3,[403,462,464],{"class":463},"sQw3B","  curl",[403,466,467],{"class":444}," -sI",[403,469,470],{"class":419}," \"https:\u002F\u002Fwww.example.com",[403,472,454],{"class":412},[403,474,475],{"class":419},"\"",[403,477,478],{"class":408}," |",[403,480,481],{"class":463}," grep",[403,483,484],{"class":444}," -iE",[403,486,487],{"class":419}," '^(cache-control|expires|last-modified):'",[403,489,478],{"class":408},[403,491,492],{"class":463}," tr",[403,494,495],{"class":419}," '\\r\\n'",[403,497,498],{"class":419}," ' '\n",[403,500,502],{"class":129,"line":501},4,[403,503,504],{"class":444},"  echo\n",[403,506,508],{"class":129,"line":507},5,[403,509,510],{"class":408},"done\n",[403,512,514],{"class":129,"line":513},6,[403,515,517],{"class":516},"sjfSM","# trade-off: a handful of URLs is a sample. Use CDN logs to list response\n",[403,519,521],{"class":129,"line":520},7,[403,522,523],{"class":516},"# headers by content type across all traffic for a complete picture.\n",[389,525,527],{"id":526},"_2-set-explicit-policies-for-every-response-class","2. Set explicit policies for every response class",[394,529,533],{"className":530,"code":531,"language":532,"meta":399,"style":399},"language-nginx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","location ~* \\.[0-9a-f]{8,}\\.(js|css|woff2|avif|webp|png|svg)$ {\n  add_header Cache-Control \"public, max-age=31536000, immutable\";   # hashed assets\n}\nlocation ~* \\.(js|css)$ {\n  add_header Cache-Control \"public, max-age=0, must-revalidate\";    # unhashed: always revalidate\n}\nlocation \u002Fapi\u002F { add_header Cache-Control \"no-cache\"; }\nlocation \u002F { add_header Cache-Control \"public, max-age=0, s-maxage=300, stale-while-revalidate=3600\"; }\n# trade-off: regex location ordering in nginx matters; test that hashed assets\n# match the first rule and not the unhashed one.\n","nginx",[30,534,535,549,566,571,583,598,602,623,642,648],{"__ignoreMap":399},[403,536,537,540,543,546],{"class":129,"line":405},[403,538,539],{"class":408},"location",[403,541,542],{"class":408}," ~*",[403,544,545],{"class":419}," \\.[0-9a-f]",[403,547,548],{"class":412},"{8,}\\.(js|css|woff2|avif|webp|png|svg)$ {\n",[403,550,551,554,557,560,563],{"class":129,"line":441},[403,552,553],{"class":408},"  add_header ",[403,555,556],{"class":412},"Cache-Control ",[403,558,559],{"class":419},"\"public, max-age=31536000, immutable\"",[403,561,562],{"class":412},";   ",[403,564,565],{"class":516},"# hashed assets\n",[403,567,568],{"class":129,"line":460},[403,569,570],{"class":412},"}\n",[403,572,573,575,577,580],{"class":129,"line":501},[403,574,539],{"class":408},[403,576,542],{"class":408},[403,578,579],{"class":419}," \\.(js|css)$ ",[403,581,582],{"class":412},"{\n",[403,584,585,587,589,592,595],{"class":129,"line":507},[403,586,553],{"class":408},[403,588,556],{"class":412},[403,590,591],{"class":419},"\"public, max-age=0, must-revalidate\"",[403,593,594],{"class":412},";    ",[403,596,597],{"class":516},"# unhashed: always revalidate\n",[403,599,600],{"class":129,"line":513},[403,601,570],{"class":412},[403,603,604,606,609,612,615,617,620],{"class":129,"line":520},[403,605,539],{"class":408},[403,607,608],{"class":463}," \u002Fapi\u002F ",[403,610,611],{"class":412},"{",[403,613,614],{"class":408}," add_header ",[403,616,556],{"class":412},[403,618,619],{"class":419},"\"no-cache\"",[403,621,622],{"class":412},"; }\n",[403,624,626,628,631,633,635,637,640],{"class":129,"line":625},8,[403,627,539],{"class":408},[403,629,630],{"class":463}," \u002F ",[403,632,611],{"class":412},[403,634,614],{"class":408},[403,636,556],{"class":412},[403,638,639],{"class":419},"\"public, max-age=0, s-maxage=300, stale-while-revalidate=3600\"",[403,641,622],{"class":412},[403,643,645],{"class":129,"line":644},9,[403,646,647],{"class":516},"# trade-off: regex location ordering in nginx matters; test that hashed assets\n",[403,649,651],{"class":129,"line":650},10,[403,652,653],{"class":516},"# match the first rule and not the unhashed one.\n",[15,655,656],{},"Expected outcome: every response states its intended lifetime; no cache has to guess.",[389,658,660],{"id":659},"_3-move-stable-name-assets-to-hashed-filenames","3. Move stable-name assets to hashed filenames",[15,662,663],{},"Unhashed assets are the main victims of heuristic caching. Fingerprint them in the build so they can be cached for a year and updated instantly by changing the reference.",[389,665,667],{"id":666},"_4-set-cdn-defaults-explicitly","4. Set CDN defaults explicitly",[15,669,670],{},"Configure the CDN's default TTL for responses without headers (or to respect origin headers strictly) so behaviour is predictable.",[15,672,673],{},[54,674,61,677,61,680,61,683,61,685,61,688,61,690,61,698,61,703,61,706,61,709,61,713,61,717,61,719,61,723,61,727,61,730,61,734,61,738,61,745,61,749,61,753,61,758,61,760,61,763,61,767,61,770,61,773,61,776,61],{"viewBox":675,"width":57,"role":58,"ariaLabel":676,"style":60},"0 0 760 318","Four steps to remove heuristic freshness from a site.",[63,678],{"className":679,"x":67,"y":67,"width":57,"height":57,"fill":68},[66],[70,681,682],{},"Eliminating heuristic caching",[74,684,676],{},[63,686],{"x":78,"y":78,"width":79,"height":687,"rx":81,"fill":82,"stroke":83,"style":84},"316",[86,689,682],{"x":88,"y":89,"fill":83,"style":90},[63,691],{"x":692,"y":99,"width":693,"height":694,"rx":695,"fill":696,"stroke":696,"style":697},"72.0","660.0","51.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[86,699,702],{"x":316,"y":700,"fill":83,"style":701,"textAnchor":298},"77.0","font-size:13px;font-weight:700","Audit headers by content type",[86,704,705],{"x":316,"y":118,"fill":83,"style":326,"textAnchor":298},"Find every response class without explicit freshness",[63,707],{"x":692,"y":708,"width":693,"height":694,"rx":695,"fill":696,"stroke":696,"style":697},"119.0",[86,710,712],{"x":316,"y":711,"fill":83,"style":701,"textAnchor":298},"140.0","Explicit policy per class",[86,714,716],{"x":316,"y":715,"fill":83,"style":326,"textAnchor":298},"157.0","Hashed assets immutable; HTML and APIs revalidate",[63,718],{"x":692,"y":166,"width":693,"height":694,"rx":695,"fill":696,"stroke":696,"style":697},[86,720,722],{"x":316,"y":721,"fill":83,"style":701,"textAnchor":298},"203.0","Hash filenames for static assets",[86,724,726],{"x":316,"y":725,"fill":83,"style":326,"textAnchor":298},"220.0","Stable names are what make heuristics dangerous",[63,728],{"x":692,"y":729,"width":693,"height":694,"rx":695,"fill":696,"stroke":696,"style":697},"245.0",[86,731,733],{"x":316,"y":732,"fill":83,"style":701,"textAnchor":298},"266.0","Explicit CDN defaults",[86,735,737],{"x":316,"y":736,"fill":83,"style":326,"textAnchor":298},"283.0","No vendor-chosen TTLs for unlabelled responses",[129,739],{"x1":740,"y1":741,"x2":740,"y2":742,"stroke":83,"strokeWidth":743,"style":744},"43.0","95.5","130.5","1.5","stroke-opacity:0.3",[129,746],{"x1":740,"y1":747,"x2":740,"y2":748,"stroke":83,"strokeWidth":743,"style":744},"158.5","193.5",[129,750],{"x1":740,"y1":751,"x2":740,"y2":752,"stroke":83,"strokeWidth":743,"style":744},"221.5","256.5",[754,755],"circle",{"cx":740,"cy":756,"r":757,"fill":696},"81.5","13",[86,759,78],{"x":740,"y":316,"fill":68,"style":701,"textAnchor":109},[754,761],{"cx":740,"cy":762,"r":757,"fill":696},"144.5",[86,764,766],{"x":740,"y":765,"fill":68,"style":701,"textAnchor":109},"149.0","2",[754,768],{"cx":740,"cy":769,"r":757,"fill":696},"207.5",[86,771,102],{"x":740,"y":772,"fill":68,"style":701,"textAnchor":109},"212.0",[754,774],{"cx":740,"cy":775,"r":757,"fill":696},"270.5",[86,777,779],{"x":740,"y":778,"fill":68,"style":701,"textAnchor":109},"275.0","4",[170,781,783],{"id":782},"verification","Verification",[15,785,786,787,789],{},"Re-run the audit: every response class should carry explicit ",[30,788,32],{},". Deploy a change to a previously unhashed file and confirm all clients pick it up immediately (because it is now hashed or revalidated). Check DevTools for resources cached without explicit headers — there should be none.",[170,791,793],{"id":792},"worked-example-the-week-long-stylesheet","Worked Example: The Week-Long Stylesheet",[15,795,796,797,800,801,249,803,805],{},"A company site served ",[30,798,799],{},"\u002Fassets\u002Fsite.css"," with ",[30,802,40],{},[30,804,32],{},". The stylesheet had not changed for eight months, giving it a heuristic lifetime of over three weeks in browsers. A redesign changed the HTML structure and the stylesheet together; returning visitors received new HTML with the old stylesheet and saw a broken layout for days. Support tickets spiked. The team fingerprinted all assets, served them as immutable, and set explicit revalidation for anything unhashed. The next redesign shipped without a single stale-asset complaint.",[170,807,809],{"id":808},"heuristics-and-performance","Heuristics and Performance",[15,811,812,813,815],{},"Heuristic freshness is not only a correctness hazard; it also leaves performance on the table. Responses without ",[30,814,40],{}," get no heuristic lifetime at all, so they are re-downloaded or revalidated on every use even if they never change. Explicit, long lifetimes for hashed assets and explicit revalidation for everything else is both faster and safer than letting caches guess. The audit in step 1 frequently uncovers fonts, images and scripts that could have been cached for a year and are instead being revalidated on every page view.",[170,817,819],{"id":818},"common-mistakes","Common Mistakes",[175,821,822,833,839,845],{},[178,823,824,830,831,252],{},[181,825,826,827,829],{},"Relying on ",[30,828,36],{}," alone."," It works but depends on clock accuracy; prefer ",[30,832,190],{},[178,834,835,838],{},[181,836,837],{},"Assuming no header means no caching."," It often means heuristic caching.",[178,840,841,844],{},[181,842,843],{},"Fixing HTML but not assets."," Unhashed JS and CSS are where heuristic caching causes the worst breakage.",[178,846,847,850],{},[181,848,849],{},"Forgetting error responses."," 404s and 5xx without headers may also be cached by some CDNs.",[170,852,854],{"id":853},"edge-cases","Edge Cases",[15,856,857,860],{},[181,858,859],{},"Responses with status 200 vs 301\u002F404."," Heuristic caching applies to responses that are cacheable by default (including some redirects and 404s); give them explicit, short TTLs.",[15,862,863,866],{},[181,864,865],{},"Query strings."," Historically some caches refused heuristic caching for URLs with query strings; behaviour varies, another reason to be explicit.",[15,868,869,872],{},[181,870,871],{},"Third-party assets."," You cannot set headers on others' files; self-hosting or proxying lets you control them.",[15,874,875,878],{},[181,876,877],{},"Range requests for media."," Large media files without headers may be partially cached inconsistently; set explicit policies for media too.",[170,880,882],{"id":881},"faq","FAQ",[884,885,888,892],"details",{"className":886},[887],"faq-item",[889,890,891],"summary",{},"Is heuristic caching part of the HTTP standard?",[15,893,894,895,897],{},"Yes. The specification allows caches to assign heuristic freshness to responses without explicit lifetimes and suggests 10% of the time since ",[30,896,40],{}," as a typical value. It is legal and widely implemented, which is exactly why it causes surprises.",[884,899,901,904],{"className":900},[887],[889,902,903],{},"Does no-cache stop heuristic caching?",[15,905,906,907,191,909,191,911,913,914,917],{},"Yes — any explicit directive (",[30,908,197],{},[30,910,190],{},[30,912,201],{},") replaces the heuristic. Even ",[30,915,916],{},"max-age=0"," is explicit.",[884,919,921,924],{"className":920},[887],[889,922,923],{},"Do CDNs use the same 10% heuristic?",[15,925,926],{},"Not necessarily. Many apply a configurable default TTL to responses without headers, or do not cache them. Check your CDN's documentation and configure it explicitly.",[884,928,930,933],{"className":929},[887],[889,931,932],{},"How can I see the heuristic lifetime a browser chose?",[15,934,935],{},"Browsers do not expose it directly. You can infer it from DevTools (whether a resource is served from cache without a request) and from the formula, but the practical answer is to remove the need by setting explicit headers.",[884,937,939,942],{"className":938},[887],[889,940,941],{},"Is it safe to add immutable to existing unhashed URLs?",[15,943,944,945,948],{},"No. ",[30,946,947],{},"immutable"," tells browsers never to revalidate during the TTL, so updates would not reach users. Use it only for content-addressed (hashed) URLs.",[884,950,952,955],{"className":951},[887],[889,953,954],{},"Which response types most often lack explicit headers?",[15,956,957],{},"In audits, the usual suspects are unhashed static files served by a default web server configuration, fonts, favicons and manifest files, JSON configuration fetched at startup, and redirect responses. Error pages generated by frameworks or load balancers are another common gap.",[884,959,961,964],{"className":960},[887],[889,962,963],{},"Can a CDN add headers for me?",[15,965,966,967,969],{},"Yes — most CDNs can set or override ",[30,968,32],{}," per path with rules or edge code. That is a quick fix for origins you cannot change, but keep the policy documented next to the origin configuration so the two do not drift apart.",[170,971,973],{"id":972},"related","Related",[175,975,976,983,990],{},[178,977,978,982],{},[19,979,981],{"href":980},"\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 right policy for fingerprinted files.",[178,984,985,989],{},[19,986,988],{"href":987},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Finvalidating-immutable-hashed-assets-safely\u002F","Invalidating immutable hashed assets safely"," — why hashing replaces invalidation.",[178,991,992,996],{},[19,993,995],{"href":994},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents\u002F","Cache-Control for HTML documents"," — the explicit HTML policy.",[998,999,1001],"script",{"type":1000},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Heuristic Freshness: What Happens When Cache-Control Is Missing\",\n  \"description\": \"How browsers and CDNs guess cache lifetimes when no explicit freshness is given, the bugs it causes, and how to audit and eliminate it.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Audit freshness headers by resource type\",\n      \"text\": \"Audit freshness headers by resource type\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Set explicit policies for every response class\",\n      \"text\": \"Expected outcome: every response states its intended lifetime; no cache has to guess.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Move stable-name assets to hashed filenames\",\n      \"text\": \"Unhashed assets are the main victims of heuristic caching.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Set CDN defaults explicitly\",\n      \"text\": \"Configure the CDN's default TTL for responses without headers (or to respect origin headers strictly) so behaviour is predictable.\"\n    }\n  ]\n}\n",[998,1003,1004],{"type":1000},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Heuristic Freshness: What Happens When Cache-Control Is Missing\",\n  \"description\": \"How browsers and CDNs guess cache lifetimes when no explicit freshness is given, the bugs it causes, and how to audit and eliminate it.\",\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\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing\u002F\"\n  }\n}\n",[998,1006,1007],{"type":1000},"\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\": \"HTTP Cache-Control Headers Explained\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Heuristic Freshness When Cache-Control Is Missing\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing\u002F\"\n    }\n  ]\n}\n",[1009,1010,1011],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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":399,"searchDepth":441,"depth":441,"links":1013},[1014,1015,1016,1022,1023,1024,1025,1026,1027,1028],{"id":172,"depth":441,"text":173},{"id":239,"depth":441,"text":240},{"id":386,"depth":441,"text":387,"children":1017},[1018,1019,1020,1021],{"id":391,"depth":460,"text":392},{"id":526,"depth":460,"text":527},{"id":659,"depth":460,"text":660},{"id":666,"depth":460,"text":667},{"id":782,"depth":441,"text":783},{"id":792,"depth":441,"text":793},{"id":808,"depth":441,"text":809},{"id":818,"depth":441,"text":819},{"id":853,"depth":441,"text":854},{"id":881,"depth":441,"text":882},{"id":972,"depth":441,"text":973},"How browsers and CDNs guess cache lifetimes when no explicit freshness is given, the bugs it causes, and how to audit and eliminate it.","md",{"slug":1032,"type":1033,"breadcrumb":1034,"datePublished":1042,"dateModified":1042},"heuristic-freshness-when-cache-control-is-missing","article",[1035,1038,1039,1040],{"name":1036,"url":1037},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1041},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing",{"title":5,"description":1046},"Responses without Cache-Control may be cached by heuristic — often 10% of the time since Last-Modified. How it causes stale pages, and explicit fixes.","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fheuristic-freshness-when-cache-control-is-missing\u002Findex","QrdQZaXgN5WMayLQsCB10cEr4do68X7QsypCTrGupZY",[1050,1054],{"title":1051,"path":1052,"stem":1053},"ETag vs Last-Modified Validators","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fetag-vs-last-modified-validators","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fetag-vs-last-modified-validators\u002Findex",{"title":1055,"path":1056,"stem":1057},"no-cache vs no-store vs max-age=0","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fno-cache-vs-no-store-vs-max-age-0","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fno-cache-vs-no-store-vs-max-age-0\u002Findex",1791308075011]