[{"data":1,"prerenderedAt":904},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents":896},{"id":4,"title":5,"body":6,"description":876,"extension":877,"meta":878,"navigation":889,"path":890,"seo":891,"stem":894,"__hash__":895},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents\u002Findex.md","Cache-Control for HTML Documents",{"type":7,"value":8,"toc":860},"minimark",[9,14,29,41,135,140,197,201,211,217,223,229,321,325,330,396,399,403,410,414,421,497,501,508,617,621,628,632,646,650,679,683,689,695,705,711,715,739,748,757,774,789,798,810,819,823,845,850,853,856],[10,11,13],"h1",{"id":12},"cache-control-for-html-documents-a-practical-deploy-safe-policy","Cache-Control for HTML Documents: A Practical, Deploy-Safe Policy",[15,16,17,18,23,24,28],"p",{},"This guide assembles the pieces of ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002F","HTTP Cache-Control Headers Explained"," into a policy for the hardest resource to cache, within ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". Hashed assets are easy: their URLs change when content changes, so they can be cached for a year. HTML is different. Its URL never changes, it references the current release's assets, and it must update promptly when content or code changes. Cache it too long and visitors see stale pages that reference deleted assets; do not cache it at all and every visit pays origin TTFB, which sits directly in the first phase of LCP.",[15,30,31,32,36,37,40],{},"The policy that works for most sites splits responsibilities: browsers keep HTML only briefly (or revalidate each time), the CDN keeps it for longer and is purged on change, validators make revalidations cheap, and ",[33,34,35],"code",{},"stale-while-revalidate"," hides refresh latency. Each piece addresses a specific failure mode, and the combination delivers sub-",[33,38,39],{},"200ms"," TTFB for most visits without risking stale releases.",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,89,51,96,51,101,51,107,51,111,51,114,51,118,51,122,51,125,51,128,51,132,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 268","100%","img","The layers of a robust HTML caching policy from browser TTL to purging.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[53,54],"rect",{"className":55,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],"svg-canvas","0","#ffffff",[60,61,62],"title",{},"Layers of an HTML caching policy",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","266","10","none","currentColor","stroke-opacity:0.18",[76,77,62],"text",{"x":78,"y":79,"fill":73,"style":80},"28.0","34.0","font-size:16px;font-weight:700",[53,82],{"x":78,"y":83,"width":84,"height":85,"rx":86,"fill":87,"stroke":87,"style":88},"56.0","704.0","40.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[76,90,95],{"x":91,"y":92,"fill":73,"style":93,"textAnchor":94},"42.0","80.5","font-size:13px;font-weight:700","start","Browser: max-age=0 + ETag",[76,97,100],{"x":98,"y":92,"fill":73,"style":99,"textAnchor":94},"233.5","font-size:12px","always revalidates, cheaply — browsers cannot be purged",[53,102],{"x":78,"y":103,"width":84,"height":85,"rx":86,"fill":104,"stroke":105,"style":106},"106.0","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[76,108,110],{"x":91,"y":109,"fill":73,"style":93,"textAnchor":94},"130.5","CDN: s-maxage=600",[76,112,113],{"x":98,"y":109,"fill":73,"style":99,"textAnchor":94},"serves from the edge for minutes — the TTFB win",[53,115],{"x":78,"y":116,"width":84,"height":85,"rx":86,"fill":73,"stroke":73,"style":117},"156.0","fill-opacity:0.06;stroke-opacity:0.4",[76,119,121],{"x":91,"y":120,"fill":73,"style":93,"textAnchor":94},"180.5","CDN: stale-while-revalidate",[76,123,124],{"x":98,"y":120,"fill":73,"style":99,"textAnchor":94},"refreshes in the background so nobody waits for a refresh",[53,126],{"x":78,"y":127,"width":84,"height":85,"rx":86,"fill":87,"stroke":87,"style":88},"206.0",[76,129,131],{"x":91,"y":130,"fill":73,"style":93,"textAnchor":94},"230.5","Purge on deploy and publish",[76,133,134],{"x":98,"y":130,"fill":73,"style":99,"textAnchor":94},"tag or URL purge so changes appear immediately",[136,137,139],"h2",{"id":138},"rapid-diagnosis","Rapid Diagnosis",[141,142,143,163,180,186],"ul",{},[144,145,146,150,151,154,155,158,159,162],"li",{},[147,148,149],"strong",{},"Read current HTML headers"," for each template (",[33,152,153],{},"curl -sI","). Typical problems: ",[33,156,157],{},"no-store"," everywhere, ",[33,160,161],{},"max-age=3600"," for browsers, or nothing at all.",[144,164,165,168,169,172,173,172,176,179],{},[147,166,167],{},"Check CDN status"," for HTML (",[33,170,171],{},"HIT","\u002F",[33,174,175],{},"MISS",[33,177,178],{},"DYNAMIC","). If HTML is never cached at the edge, TTFB equals origin latency.",[144,181,182,185],{},[147,183,184],{},"Deploy and test staleness."," After a deploy, how long until a browser that visited before sees the new page? If the answer is \"up to an hour\", browser TTLs are too long.",[144,187,188,191,192,196],{},[147,189,190],{},"Check for broken pages after deploys."," Stale HTML referencing deleted asset hashes is the classic symptom — see ",[19,193,195],{"href":194},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Ffixing-stale-html-after-a-deploy\u002F","fixing stale HTML after a deploy",".",[136,198,200],{"id":199},"root-cause-analysis","Root Cause Analysis",[15,202,203,206,207,210],{},[147,204,205],{},"1. Browser TTLs on HTML."," A ",[33,208,209],{},"max-age"," of minutes or hours on HTML means browsers keep old versions that you cannot purge.",[15,212,213,216],{},[147,214,215],{},"2. Uncached HTML at the CDN."," Default CDN behaviour skips HTML; without explicit rules every request goes to the origin.",[15,218,219,222],{},[147,220,221],{},"3. Personalisation in shared HTML."," Pages that read cookies cannot be cached publicly until personal parts are split out.",[15,224,225,228],{},[147,226,227],{},"4. No purge path."," Without purging, teams keep CDN TTLs short, which keeps hit ratios low.",[15,230,231],{},[44,232,51,235,51,238,51,241,51,243,51,246,51,248,51,258,51,265,51,273,51,278,51,282,51,286,51,290,51,294,51,299,51,303,51,307,51,311,51,315,51],{"viewBox":233,"width":47,"role":48,"ariaLabel":234,"style":50},"0 0 760 216","Bar chart of p75 time to first byte for the same article template under four HTML caching policies.",[53,236],{"className":237,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,239,240],{},"TTFB p75 for an article template by HTML policy",[64,242,234],{},[53,244],{"x":68,"y":68,"width":69,"height":245,"rx":71,"fill":72,"stroke":73,"style":74},"214",[76,247,240],{"x":78,"y":79,"fill":73,"style":80},[249,250],"line",{"x1":251,"y1":252,"x2":251,"y2":253,"stroke":105,"strokeWidth":254,"strokeDashArray":255,"style":257},"348.7","72.0","194.0","1.5",[256,256],"4","stroke-opacity:0.9",[76,259,264],{"x":260,"y":261,"fill":73,"style":262,"textAnchor":263},"184.6","92.0","font-size:13px","end","no-store (origin every time)",[53,266],{"x":267,"y":268,"width":269,"height":270,"rx":271,"fill":104,"stroke":105,"style":272},"196.6","78.0","471.4","19","3","fill-opacity:0.7;stroke-opacity:0.9",[76,274,277],{"x":275,"y":261,"fill":73,"style":276},"674.0","font-size:12px;font-weight:600","620ms",[76,279,281],{"x":260,"y":280,"fill":73,"style":262,"textAnchor":263},"123.0","no-cache, no CDN caching",[53,283],{"x":267,"y":284,"width":285,"height":270,"rx":271,"fill":104,"stroke":105,"style":272},"109.0","410.6",[76,287,289],{"x":288,"y":280,"fill":73,"style":276},"613.2","540ms",[76,291,293],{"x":260,"y":292,"fill":73,"style":262,"textAnchor":263},"154.0","max-age=0, s-maxage=600",[53,295],{"x":267,"y":296,"width":297,"height":270,"rx":271,"fill":87,"stroke":87,"style":298},"140.0","83.6","fill-opacity:0.55;stroke-opacity:0.9",[76,300,302],{"x":301,"y":292,"fill":73,"style":276},"286.3","110ms",[76,304,306],{"x":260,"y":305,"fill":73,"style":262,"textAnchor":263},"185.0","+ stale-while-revalidate",[53,308],{"x":267,"y":309,"width":310,"height":270,"rx":271,"fill":87,"stroke":87,"style":298},"171.0","64.6",[76,312,314],{"x":313,"y":305,"fill":73,"style":276},"267.2","85ms",[76,316,39],{"x":251,"y":317,"fill":318,"style":319,"textAnchor":320},"68.0","#8a6608","font-size:12px;font-weight:700","middle",[136,322,324],{"id":323},"step-by-step-resolution","Step-by-Step Resolution",[326,327,329],"h3",{"id":328},"_1-set-the-split-policy-on-public-html","1. Set the split policy on public HTML",[331,332,337],"pre",{"className":333,"code":334,"language":335,"meta":336,"style":336},"language-nginx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","location \u002F {\n  add_header Cache-Control \"public, max-age=0, must-revalidate, s-maxage=600, stale-while-revalidate=86400, stale-if-error=86400\" always;\n  # trade-off: s-maxage=600 means a missed purge can leave stale HTML at the edge\n  # for up to ten minutes. That is acceptable only because purges run on every\n  # deploy and publish; without them, use a much shorter s-maxage.\n}\n","nginx","",[33,338,339,355,371,378,384,390],{"__ignoreMap":336},[340,341,343,347,351],"span",{"class":249,"line":342},1,[340,344,346],{"class":345},"sPARh","location",[340,348,350],{"class":349},"sQw3B"," \u002F ",[340,352,354],{"class":353},"saISM","{\n",[340,356,358,361,364,368],{"class":249,"line":357},2,[340,359,360],{"class":345},"  add_header ",[340,362,363],{"class":353},"Cache-Control ",[340,365,367],{"class":366},"sZ8jY","\"public, max-age=0, must-revalidate, s-maxage=600, stale-while-revalidate=86400, stale-if-error=86400\"",[340,369,370],{"class":353}," always;\n",[340,372,374],{"class":249,"line":373},3,[340,375,377],{"class":376},"sjfSM","  # trade-off: s-maxage=600 means a missed purge can leave stale HTML at the edge\n",[340,379,381],{"class":249,"line":380},4,[340,382,383],{"class":376},"  # for up to ten minutes. That is acceptable only because purges run on every\n",[340,385,387],{"class":249,"line":386},5,[340,388,389],{"class":376},"  # deploy and publish; without them, use a much shorter s-maxage.\n",[340,391,393],{"class":249,"line":392},6,[340,394,395],{"class":353},"}\n",[15,397,398],{},"Expected outcome: browsers revalidate against the nearby CDN; the CDN serves cached HTML and refreshes in the background.",[326,400,402],{"id":401},"_2-emit-validators","2. Emit validators",[15,404,405,406,196],{},"Make sure HTML responses carry a stable ETag so browser revalidations against the CDN return 304s — see ",[19,407,409],{"href":408},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fetag-vs-last-modified-validators\u002F","ETag vs Last-Modified validators",[326,411,413],{"id":412},"_3-purge-on-deploy-and-on-content-changes","3. Purge on deploy and on content changes",[15,415,416,417,420],{},"On deploy, purge HTML (by tag such as ",[33,418,419],{},"html"," or by template tags); on content publish, purge the affected pages' tags. Never purge hashed assets.",[331,422,426],{"className":423,"code":424,"language":425,"meta":336,"style":336},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Cloudflare example: purge all HTML tagged at the origin with Cache-Tag: html.\ncurl -sX POST \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Fzones\u002F$ZONE\u002Fpurge_cache\" \\\n  -H \"Authorization: Bearer $CF_TOKEN\" -H \"Content-Type: application\u002Fjson\" \\\n  --data '{\"tags\":[\"html\"]}'\n# trade-off: purging all HTML on every deploy causes a brief burst of origin\n# traffic; tiered caching and request collapsing keep it manageable.\n","bash",[33,427,428,433,457,479,487,492],{"__ignoreMap":336},[340,429,430],{"class":249,"line":342},[340,431,432],{"class":376},"# Cloudflare example: purge all HTML tagged at the origin with Cache-Tag: html.\n",[340,434,435,438,442,445,448,451,454],{"class":249,"line":357},[340,436,437],{"class":349},"curl",[340,439,441],{"class":440},"sPXB4"," -sX",[340,443,444],{"class":366}," POST",[340,446,447],{"class":366}," \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Fzones\u002F",[340,449,450],{"class":353},"$ZONE",[340,452,453],{"class":366},"\u002Fpurge_cache\"",[340,455,456],{"class":345}," \\\n",[340,458,459,462,465,468,471,474,477],{"class":249,"line":373},[340,460,461],{"class":440},"  -H",[340,463,464],{"class":366}," \"Authorization: Bearer ",[340,466,467],{"class":353},"$CF_TOKEN",[340,469,470],{"class":366},"\"",[340,472,473],{"class":440}," -H",[340,475,476],{"class":366}," \"Content-Type: application\u002Fjson\"",[340,478,456],{"class":345},[340,480,481,484],{"class":249,"line":380},[340,482,483],{"class":440},"  --data",[340,485,486],{"class":366}," '{\"tags\":[\"html\"]}'\n",[340,488,489],{"class":249,"line":386},[340,490,491],{"class":376},"# trade-off: purging all HTML on every deploy causes a brief burst of origin\n",[340,493,494],{"class":249,"line":392},[340,495,496],{"class":376},"# traffic; tiered caching and request collapsing keep it manageable.\n",[326,498,500],{"id":499},"_4-use-private-policies-for-personalised-html","4. Use private policies for personalised HTML",[15,502,503,504,507],{},"For logged-in or personalised pages that cannot be split yet, use ",[33,505,506],{},"private, no-cache"," — browser revalidation only, no CDN caching — and plan to move them to a shared-shell architecture.",[15,509,510],{},[44,511,51,514,51,517,51,520,51,522,51,525,51,527,51,531,51,537,51,541,51,545,51,548,51,552,51,556,51,560,51,562,51,565,51,567,51,570,51,573,51,577,51,579,51,581,51,583,51,586,51,589,51,593,51,595,51,597,51,599,51,602,51,605,51,609,51,611,51,613,51,615,51],{"viewBox":512,"width":47,"role":48,"ariaLabel":513,"style":50},"0 0 760 228","Recommended Cache-Control values for different kinds of HTML pages.",[53,515],{"className":516,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,518,519],{},"HTML policies by page type",[64,521,513],{},[53,523],{"x":68,"y":68,"width":69,"height":524,"rx":71,"fill":72,"stroke":73,"style":74},"226",[76,526,519],{"x":78,"y":79,"fill":73,"style":80},[53,528],{"x":78,"y":83,"width":529,"height":530,"rx":57,"fill":73,"stroke":73,"style":117},"187.9","30.0",[76,532,536],{"x":533,"y":534,"fill":73,"style":535,"textAnchor":94},"38.0","75.5","font-size:12.5px;font-weight:700","Page type",[53,538],{"x":539,"y":83,"width":540,"height":530,"rx":57,"fill":73,"stroke":73,"style":117},"215.9","258.1",[76,542,544],{"x":543,"y":534,"fill":73,"style":535,"textAnchor":320},"344.9","Browser",[53,546],{"x":547,"y":83,"width":540,"height":530,"rx":57,"fill":73,"stroke":73,"style":117},"474.0",[76,549,551],{"x":550,"y":534,"fill":73,"style":535,"textAnchor":320},"603.0","CDN",[53,553],{"x":78,"y":554,"width":529,"height":530,"rx":57,"fill":72,"stroke":73,"style":555},"86.0","stroke-opacity:0.35",[76,557,559],{"x":533,"y":558,"fill":73,"style":535,"textAnchor":94},"105.5","Public content and marketing",[53,561],{"x":539,"y":554,"width":540,"height":530,"rx":57,"fill":87,"stroke":87,"style":88},[76,563,564],{"x":543,"y":558,"fill":73,"style":99,"textAnchor":320},"max-age=0 + ETag",[53,566],{"x":547,"y":554,"width":540,"height":530,"rx":57,"fill":87,"stroke":87,"style":88},[76,568,569],{"x":550,"y":558,"fill":73,"style":99,"textAnchor":320},"s-maxage=600 + SWR + purge",[53,571],{"x":78,"y":572,"width":529,"height":530,"rx":57,"fill":72,"stroke":73,"style":555},"116.0",[76,574,576],{"x":533,"y":575,"fill":73,"style":535,"textAnchor":94},"135.5","Listings and search",[53,578],{"x":539,"y":572,"width":540,"height":530,"rx":57,"fill":87,"stroke":87,"style":88},[76,580,564],{"x":543,"y":575,"fill":73,"style":99,"textAnchor":320},[53,582],{"x":547,"y":572,"width":540,"height":530,"rx":57,"fill":73,"stroke":73,"style":117},[76,584,585],{"x":550,"y":575,"fill":73,"style":99,"textAnchor":320},"s-maxage=60 + SWR",[53,587],{"x":78,"y":588,"width":529,"height":530,"rx":57,"fill":72,"stroke":73,"style":555},"146.0",[76,590,592],{"x":533,"y":591,"fill":73,"style":535,"textAnchor":94},"165.5","Personalised (not yet split)",[53,594],{"x":539,"y":588,"width":540,"height":530,"rx":57,"fill":73,"stroke":73,"style":117},[76,596,506],{"x":543,"y":591,"fill":73,"style":99,"textAnchor":320},[53,598],{"x":547,"y":588,"width":540,"height":530,"rx":57,"fill":104,"stroke":105,"style":106},[76,600,601],{"x":550,"y":591,"fill":73,"style":99,"textAnchor":320},"not cached",[53,603],{"x":78,"y":604,"width":529,"height":530,"rx":57,"fill":72,"stroke":73,"style":555},"176.0",[76,606,608],{"x":533,"y":607,"fill":73,"style":535,"textAnchor":94},"195.5","Sensitive account pages",[53,610],{"x":539,"y":604,"width":540,"height":530,"rx":57,"fill":104,"stroke":105,"style":106},[76,612,157],{"x":543,"y":607,"fill":73,"style":99,"textAnchor":320},[53,614],{"x":547,"y":604,"width":540,"height":530,"rx":57,"fill":104,"stroke":105,"style":106},[76,616,601],{"x":550,"y":607,"fill":73,"style":99,"textAnchor":320},[136,618,620],{"id":619},"verification","Verification",[15,622,623,624,627],{},"After deploying the policy, request a page twice through the CDN (hit on the second request, ",[33,625,626],{},"Age"," increasing), reload in a browser (304 for HTML), and run a deploy: within seconds of the purge, a fresh request should return the new HTML. In RUM, TTFB p75 for public templates should approach edge latency, and LCP should improve correspondingly.",[136,629,631],{"id":630},"worked-example-a-marketing-site-on-a-headless-cms","Worked Example: A Marketing Site on a Headless CMS",[15,633,634,635,638,639,642,643,645],{},"A marketing site served HTML from a Node SSR server with ",[33,636,637],{},"Cache-Control: no-cache"," and no CDN rules; TTFB p75 was 480ms globally and over 900ms in Asia. The team added the split policy with ",[33,640,641],{},"s-maxage=600",", tagged pages by CMS entry, purged tags from a CMS webhook on publish and purged ",[33,644,419],{}," on deploy. TTFB p75 fell to 70ms worldwide, LCP p75 improved by 400ms, and editors saw published changes live within seconds thanks to the webhook purge.",[136,647,649],{"id":648},"common-mistakes","Common Mistakes",[141,651,652,658,664,670],{},[144,653,654,657],{},[147,655,656],{},"Long browser max-age on HTML."," You cannot purge browsers; stale versions persist for the full TTL.",[144,659,660,663],{},[147,661,662],{},"Short CDN TTLs instead of purging."," They keep hit ratios low and TTFB high.",[144,665,666,669],{},[147,667,668],{},"Purging hashed assets on deploy."," Unnecessary and harmful; only HTML (and data) need purging.",[144,671,672,678],{},[147,673,674,675,196],{},"Missing ",[33,676,677],{},"stale-if-error"," Without it, an origin outage turns cached HTML into errors once TTLs expire.",[136,680,682],{"id":681},"edge-cases","Edge Cases",[15,684,685,688],{},[147,686,687],{},"Prerendered and static sites."," Static HTML can use the same policy; purge on deploy replaces rebuilding caches.",[15,690,691,694],{},[147,692,693],{},"Streaming SSR."," Cached HTML is served whole from the edge; streaming matters only for misses.",[15,696,697,700,701,196],{},[147,698,699],{},"Early Hints."," CDNs can send 103 Early Hints for cached HTML, letting browsers start fetching critical assets even before the HTML arrives — see ",[19,702,704],{"href":703},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-103-early-hints-behind-a-cdn\u002F","using 103 Early Hints behind a CDN",[15,706,707,710],{},[147,708,709],{},"Service workers."," If a service worker caches HTML, it becomes another layer that must be updated on deploy.",[136,712,714],{"id":713},"faq","FAQ",[716,717,720,724],"details",{"className":718},[719],"faq-item",[721,722,723],"summary",{},"Why max-age=0 instead of no-cache for browsers?",[15,725,726,727,730,731,734,735,738],{},"With ",[33,728,729],{},"must-revalidate"," they behave the same for browsers; ",[33,732,733],{},"max-age=0"," pairs naturally with ",[33,736,737],{},"s-maxage"," in one header. Either is fine as long as validators are present.",[716,740,742,745],{"className":741},[719],[721,743,744],{},"How long should s-maxage be?",[15,746,747],{},"As long as your purge process is reliable — minutes to hours for content sites. Without purging, keep it short (30–60 seconds) to bound staleness.",[716,749,751,754],{"className":750},[719],[721,752,753],{},"Does stale-while-revalidate risk serving old content after a deploy?",[15,755,756],{},"Only if the purge fails; purges remove entries regardless of SWR windows. SWR applies to entries that expire naturally, letting one request trigger a background refresh while others are served immediately.",[716,758,760,763],{"className":759},[719],[721,761,762],{},"Should HTML vary on Accept-Encoding?",[15,764,765,766,769,770,773],{},"Yes — CDNs and servers handle compressed variants with ",[33,767,768],{},"Vary: Accept-Encoding",", which is safe because the value set is small. Avoid other ",[33,771,772],{},"Vary"," values on HTML.",[716,775,777,780],{"className":776},[719],[721,778,779],{},"What about HTML on a static host without purge APIs?",[15,781,782,783,785,786,788],{},"Use short ",[33,784,737],{}," (or the host's built-in invalidation on deploy) and ",[33,787,733],{}," for browsers. Many static hosts invalidate their CDN automatically on each deploy.",[716,790,792,795],{"className":791},[719],[721,793,794],{},"How do I handle HTML for A\u002FB tests?",[15,796,797],{},"Assign the arm at the edge, include it in the cache key, and keep the same split policy for each variant. Avoid client-side experiment rewrites of cached HTML, which add flicker and layout shift.",[716,799,801,804],{"className":800},[719],[721,802,803],{},"Should error pages be cached?",[15,805,806,807,809],{},"Cache 404 pages briefly (seconds to a minute) to absorb bot traffic, and never cache 5xx responses for long. Use ",[33,808,677],{}," so an origin failure serves the last good page instead of an error.",[716,811,813,816],{"className":812},[719],[721,814,815],{},"Does this policy work with HTTP\u002F3 and Early Hints?",[15,817,818],{},"Yes. Caching headers are independent of the transport, and cached HTML is the best case for 103 Early Hints because the CDN can send hints instantly from its cached knowledge of the page's critical assets.",[136,820,822],{"id":821},"related","Related",[141,824,825,831,838],{},[144,826,827,830],{},[19,828,829],{"href":194},"Fixing stale HTML after a deploy"," — what goes wrong without this policy.",[144,832,833,837],{},[19,834,836],{"href":835},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fstale-while-revalidate-implementation\u002Fchoosing-swr-windows-for-html-and-apis\u002F","Choosing SWR windows for HTML and APIs"," — tuning the stale windows.",[144,839,840,844],{},[19,841,843],{"href":842},"\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"," — extending the policy to authenticated pages.",[846,847,849],"script",{"type":848},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Cache-Control for HTML Documents: A Practical, Deploy-Safe Policy\",\n  \"description\": \"A complete, deploy-safe caching policy for HTML that keeps TTFB low through the CDN while guaranteeing visitors see new releases promptly.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Set the split policy on public HTML\",\n      \"text\": \"Expected outcome: browsers revalidate against the nearby CDN; the CDN serves cached HTML and refreshes in the background.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Emit validators\",\n      \"text\": \"Make sure HTML responses carry a stable ETag so browser revalidations against the CDN return 304s — see ETag vs Last-Modified validators.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Purge on deploy and on content changes\",\n      \"text\": \"On deploy, purge HTML (by tag such as html or by template tags); on content publish, purge the affected pages' tags.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Use private policies for personalised HTML\",\n      \"text\": \"For logged-in or personalised pages that cannot be split yet, use private, no-cache — browser revalidation only, no CDN caching — and plan to move them to a shared-shell architecture.\"\n    }\n  ]\n}\n",[846,851,852],{"type":848},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Cache-Control for HTML Documents: A Practical, Deploy-Safe Policy\",\n  \"description\": \"A complete, deploy-safe caching policy for HTML that keeps TTFB low through the CDN while guaranteeing visitors see new releases promptly.\",\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\u002Fcache-control-for-html-documents\u002F\"\n  }\n}\n",[846,854,855],{"type":848},"\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\": \"Cache-Control for HTML Documents\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents\u002F\"\n    }\n  ]\n}\n",[857,858,859],"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 .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 .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}",{"title":336,"searchDepth":357,"depth":357,"links":861},[862,863,864,870,871,872,873,874,875],{"id":138,"depth":357,"text":139},{"id":199,"depth":357,"text":200},{"id":323,"depth":357,"text":324,"children":865},[866,867,868,869],{"id":328,"depth":373,"text":329},{"id":401,"depth":373,"text":402},{"id":412,"depth":373,"text":413},{"id":499,"depth":373,"text":500},{"id":619,"depth":357,"text":620},{"id":630,"depth":357,"text":631},{"id":648,"depth":357,"text":649},{"id":681,"depth":357,"text":682},{"id":713,"depth":357,"text":714},{"id":821,"depth":357,"text":822},"A complete, deploy-safe caching policy for HTML that keeps TTFB low through the CDN while guaranteeing visitors see new releases promptly.","md",{"slug":879,"type":880,"breadcrumb":881,"datePublished":888,"dateModified":888},"cache-control-for-html-documents","article",[882,884,885,886],{"name":883,"url":172},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":887},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents",{"title":892,"description":893},"Cache-Control for HTML Documents: A Practical Policy","HTML needs freshness after deploys and speed for every visit. Combine short browser TTLs, CDN s-maxage, validators, SWR and purges into one safe HTML policy.","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fcache-control-for-html-documents\u002Findex","TaqlWeKkY6fkx45S1fcXXsIE2hj-PYq8CdUR281A7fg",[897,900],{"title":22,"path":898,"stem":899,"children":-1},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Findex",{"title":901,"path":902,"stem":903,"children":-1},"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",1791308070308]