[{"data":1,"prerenderedAt":773},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002F":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002F":765},{"id":4,"title":5,"body":6,"description":757,"extension":758,"meta":759,"navigation":760,"path":761,"seo":762,"stem":763,"__hash__":764},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002Findex.md","Setting up immutable cache headers for hashed assets",{"type":7,"value":8,"toc":748},"minimark",[9,13,30,47,52,59,73,77,80,155,159,169,179,189,195,262,268,372,378,513,516,520,523,562,569,592,602,606,624,637,644,648,681,685,694,717,738,744],[10,11,5],"h1",{"id":12},"setting-up-immutable-cache-headers-for-hashed-assets",[14,15,16,17,21,22,25,26,29],"p",{},"Modern frontend build pipelines generate content-hashed filenames (e.g., ",[18,19,20],"code",{},"app.8f3a9c.js",") to guarantee asset uniqueness across deployments. Despite this, browsers still issue conditional ",[18,23,24],{},"If-None-Match"," or ",[18,27,28],{},"If-Modified-Since"," requests on page reloads. These generate unnecessary 304 responses and add measurable network latency.",[14,31,32,33,36,37,40,41,46],{},"The ",[18,34,35],{},"immutable"," directive solves this by explicitly telling the browser that a cached resource will never change during its ",[18,38,39],{},"max-age"," period. It bypasses revalidation entirely. Before implementing, reviewing the foundational mechanics in ",[42,43,45],"a",{"href":44},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002F","HTTP Cache-Control Headers Explained"," ensures correct directive placement and prevents cache poisoning.",[48,49,51],"h2",{"id":50},"root-cause-analysis-why-hashed-assets-trigger-304s-without-immutable","Root Cause Analysis: Why Hashed Assets Trigger 304s Without immutable",[14,53,54,55,58],{},"Browsers default to a freshness validation strategy during hard reloads or back\u002Fforward navigation. Even when a hashed asset exists in the HTTP cache, the browser sends a conditional GET request. It verifies the ETag or ",[18,56,57],{},"Last-Modified"," timestamp against the origin.",[14,60,61,62,65,66,69,70,72],{},"The server responds with a ",[18,63,64],{},"304 Not Modified",". This still consumes TCP handshakes, TLS negotiation, and server CPU cycles. The ",[18,67,68],{},"Cache-Control: immutable"," directive overrides this behavior. It declares that the resource URL is permanently bound to its content hash. Once cached, the browser serves it directly from disk or memory until ",[18,71,39],{}," expires.",[48,74,76],{"id":75},"devtools-lighthouse-diagnostic-workflow","DevTools & Lighthouse Diagnostic Workflow",[14,78,79],{},"Execute this diagnostic sequence before and after header deployment to isolate validation overhead.",[81,82,83,91,102,116,123,133,152],"ul",{},[84,85,86,87,90],"li",{},"Open Chrome DevTools > Network tab. Ensure ",[18,88,89],{},"Disable cache"," is unchecked.",[84,92,93,94,97,98,101],{},"Perform a hard reload (",[18,95,96],{},"Ctrl+Shift+R"," \u002F ",[18,99,100],{},"Cmd+Shift+R",").",[84,103,104,105,25,108,111,112,115],{},"Filter by ",[18,106,107],{},"JS",[18,109,110],{},"CSS",". Identify any hashed files returning ",[18,113,114],{},"304"," status codes.",[84,117,118,119,122],{},"Run Lighthouse via the Performance tab. Locate the ",[18,120,121],{},"Serve static assets with an efficient cache policy"," audit.",[84,124,125,126,129,130,132],{},"Flag assets with ",[18,127,128],{},"max-age \u003C 31536000"," or missing the ",[18,131,35],{}," directive.",[84,134,135,139,140,143,144,147,148,151],{},[136,137,138],"strong",{},"Target thresholds:"," ",[18,141,142],{},"0%"," 304 responses for hashed assets, ",[18,145,146],{},">95%"," Lighthouse cache policy score, and ",[18,149,150],{},"\u003C50ms"," TTFB for cached resources.",[84,153,154],{},"If Lighthouse continues to flag assets, verify that intermediate proxies or misconfigured CDN cache keys are not stripping the directive.",[48,156,158],{"id":157},"step-by-step-configuration-build-tools-edge-servers","Step-by-Step Configuration: Build Tools & Edge Servers",[14,160,161,162,164,165,168],{},"Configuration must target only hashed assets. Applying ",[18,163,35],{}," to unversioned files like ",[18,166,167],{},"index.html"," or API responses causes permanent stale content delivery.",[14,170,171,174,175,178],{},[136,172,173],{},"Webpack:"," Use ",[18,176,177],{},"webpack-manifest-plugin"," to map hashes. Inject headers via a custom static file server or Node middleware.",[14,180,181,184,185,188],{},[136,182,183],{},"Vite:"," Configure ",[18,186,187],{},"build.rollupOptions.output.assetFileNames"," to include hashes. Delegate header assignment to your hosting layer or reverse proxy.",[14,190,191,194],{},[136,192,193],{},"Nginx:"," Apply a regex location block matching hash patterns.",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-nginx shiki shiki-themes github-dark-high-contrast github-dark-high-contrast github-light-high-contrast","location ~* \\.[0-9a-f]{8,}\\.(js|css|png|jpg|svg|woff2)$ {\n expires 1y;\n add_header Cache-Control \"public, max-age=31536000, immutable\";\n add_header X-Cache-Status $upstream_cache_status;\n}\n","nginx","",[18,203,204,224,233,248,256],{"__ignoreMap":201},[205,206,209,213,216,220],"span",{"class":207,"line":208},"line",1,[205,210,212],{"class":211},"sCJTb","location",[205,214,215],{"class":211}," ~*",[205,217,219],{"class":218},"sJdzJ"," \\.[0-9a-f]",[205,221,223],{"class":222},"s3sCt","{8,}\\.(js|css|png|jpg|svg|woff2)$ {\n",[205,225,227,230],{"class":207,"line":226},2,[205,228,229],{"class":211}," expires ",[205,231,232],{"class":222},"1y;\n",[205,234,236,239,242,245],{"class":207,"line":235},3,[205,237,238],{"class":211}," add_header ",[205,240,241],{"class":222},"Cache-Control ",[205,243,244],{"class":218},"\"public, max-age=31536000, immutable\"",[205,246,247],{"class":222},";\n",[205,249,251,253],{"class":207,"line":250},4,[205,252,238],{"class":211},[205,254,255],{"class":222},"X-Cache-Status $upstream_cache_status;\n",[205,257,259],{"class":207,"line":258},5,[205,260,261],{"class":222},"}\n",[14,263,264,267],{},[136,265,266],{},"Cloudflare Edge:"," Deploy a Transform Rule matching hash patterns.",[196,269,273],{"className":270,"code":271,"language":272,"meta":201,"style":201},"language-yaml shiki shiki-themes github-dark-high-contrast github-dark-high-contrast github-light-high-contrast","transforms:\n - name: immutable-assets\n rule:\n target:\n url: \"*.hash.*\"\n action:\n set_headers:\n - name: Cache-Control\n value: \"public, max-age=31536000, immutable\"\n operation: set\n","yaml",[18,274,275,284,298,305,312,322,330,338,350,361],{"__ignoreMap":201},[205,276,277,281],{"class":207,"line":208},[205,278,280],{"class":279},"sj_b3","transforms",[205,282,283],{"class":222},":\n",[205,285,286,289,292,295],{"class":207,"line":226},[205,287,288],{"class":222}," - ",[205,290,291],{"class":279},"name",[205,293,294],{"class":222},": ",[205,296,297],{"class":218},"immutable-assets\n",[205,299,300,303],{"class":207,"line":235},[205,301,302],{"class":279}," rule",[205,304,283],{"class":222},[205,306,307,310],{"class":207,"line":250},[205,308,309],{"class":279}," target",[205,311,283],{"class":222},[205,313,314,317,319],{"class":207,"line":258},[205,315,316],{"class":279}," url",[205,318,294],{"class":222},[205,320,321],{"class":218},"\"*.hash.*\"\n",[205,323,325,328],{"class":207,"line":324},6,[205,326,327],{"class":279}," action",[205,329,283],{"class":222},[205,331,333,336],{"class":207,"line":332},7,[205,334,335],{"class":279}," set_headers",[205,337,283],{"class":222},[205,339,341,343,345,347],{"class":207,"line":340},8,[205,342,288],{"class":222},[205,344,291],{"class":279},[205,346,294],{"class":222},[205,348,349],{"class":218},"Cache-Control\n",[205,351,353,356,358],{"class":207,"line":352},9,[205,354,355],{"class":279}," value",[205,357,294],{"class":222},[205,359,360],{"class":218},"\"public, max-age=31536000, immutable\"\n",[205,362,364,367,369],{"class":207,"line":363},10,[205,365,366],{"class":279}," operation",[205,368,294],{"class":222},[205,370,371],{"class":218},"set\n",[14,373,374,377],{},[136,375,376],{},"Express\u002FNode Middleware:"," Inject headers conditionally during static serving.",[196,379,383],{"className":380,"code":381,"language":382,"meta":201,"style":201},"language-javascript shiki shiki-themes github-dark-high-contrast github-dark-high-contrast github-light-high-contrast","app.use('\u002Fassets', (req, res, next) => {\n if (\u002F\\.[0-9a-f]{8,}\\.(js|css)$\u002F.test(req.path)) {\n res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');\n }\n next();\n});\n","javascript",[18,384,385,427,474,495,500,508],{"__ignoreMap":201},[205,386,387,390,394,397,400,403,407,410,413,415,418,421,424],{"class":207,"line":208},[205,388,389],{"class":222},"app.",[205,391,393],{"class":392},"sGhOu","use",[205,395,396],{"class":222},"(",[205,398,399],{"class":218},"'\u002Fassets'",[205,401,402],{"class":222},", (",[205,404,406],{"class":405},"spFnL","req",[205,408,409],{"class":222},", ",[205,411,412],{"class":405},"res",[205,414,409],{"class":222},[205,416,417],{"class":405},"next",[205,419,420],{"class":222},") ",[205,422,423],{"class":211},"=>",[205,425,426],{"class":222}," {\n",[205,428,429,432,435,438,442,446,449,451,454,457,460,463,465,468,471],{"class":207,"line":226},[205,430,431],{"class":211}," if",[205,433,434],{"class":222}," (",[205,436,437],{"class":218},"\u002F",[205,439,441],{"class":440},"s1o6E","\\.",[205,443,445],{"class":444},"s5hCx","[0-9a-f]",[205,447,448],{"class":211},"{8,}",[205,450,441],{"class":440},[205,452,453],{"class":218},"(js",[205,455,456],{"class":211},"|",[205,458,459],{"class":218},"css)",[205,461,462],{"class":211},"$",[205,464,437],{"class":218},[205,466,467],{"class":222},".",[205,469,470],{"class":392},"test",[205,472,473],{"class":222},"(req.path)) {\n",[205,475,476,479,482,484,487,489,492],{"class":207,"line":235},[205,477,478],{"class":222}," res.",[205,480,481],{"class":392},"setHeader",[205,483,396],{"class":222},[205,485,486],{"class":218},"'Cache-Control'",[205,488,409],{"class":222},[205,490,491],{"class":218},"'public, max-age=31536000, immutable'",[205,493,494],{"class":222},");\n",[205,496,497],{"class":207,"line":250},[205,498,499],{"class":222}," }\n",[205,501,502,505],{"class":207,"line":258},[205,503,504],{"class":392}," next",[205,506,507],{"class":222},"();\n",[205,509,510],{"class":207,"line":324},[205,511,512],{"class":222},"});\n",[14,514,515],{},"Always verify the exact header string in the raw response before deployment.",[48,517,519],{"id":518},"metric-thresholds-production-validation","Metric Thresholds & Production Validation",[14,521,522],{},"Monitor CDN analytics and Real User Monitoring (RUM) data immediately post-deployment. Validate against these strict thresholds:",[81,524,525,534,543,552],{},[84,526,527,139,530,533],{},[136,528,529],{},"CDN cache hit ratio:",[18,531,532],{},"≥98%"," for static assets.",[84,535,536,139,539,542],{},[136,537,538],{},"TTFB for cached hashed files:",[18,540,541],{},"0-10ms"," (served directly from browser cache).",[84,544,545,139,548,551],{},[136,546,547],{},"Network payload reduction:",[18,549,550],{},">40%"," on repeat visits.",[84,553,554,557,558,561],{},[136,555,556],{},"Core Web Vitals:"," LCP variance typically drops by ",[18,559,560],{},"15-30ms"," on 3G\u002F4G networks when validation jitter is eliminated.",[14,563,564,565,568],{},"Verify header persistence using ",[18,566,567],{},"curl",":",[196,570,574],{"className":571,"code":572,"language":573,"meta":201,"style":201},"language-bash shiki shiki-themes github-dark-high-contrast github-dark-high-contrast github-light-high-contrast","curl -I -H 'Cache-Control: max-age=0' https:\u002F\u002Fyourdomain.com\u002Fassets\u002Fapp.[hash].js\n","bash",[18,575,576],{"__ignoreMap":201},[205,577,578,580,583,586,589],{"class":207,"line":208},[205,579,567],{"class":405},[205,581,582],{"class":444}," -I",[205,584,585],{"class":444}," -H",[205,587,588],{"class":218}," 'Cache-Control: max-age=0'",[205,590,591],{"class":218}," https:\u002F\u002Fyourdomain.com\u002Fassets\u002Fapp.[hash].js\n",[14,593,594,595,598,599,601],{},"The response must return ",[18,596,597],{},"200 OK"," with the ",[18,600,35],{}," directive intact. This proves the directive overrides conditional requests even when clients request fresh validation.",[48,603,605],{"id":604},"edge-cases-integration-with-service-workers","Edge Cases & Integration with Service Workers",[14,607,608,609,612,613,615,616,619,620,623],{},"Service workers intercept ",[18,610,611],{},"fetch"," requests and can bypass browser cache headers if misconfigured. Ensure your SW ",[18,614,611],{}," handler respects ",[18,617,618],{},"request.cache"," or explicitly checks ",[18,621,622],{},"caches.match()"," before falling back to the network.",[14,625,626,627,629,630,633,634,636],{},"When combining ",[18,628,35],{}," with ",[18,631,632],{},"stale-while-revalidate",", apply ",[18,635,35],{}," only to the hashed payload layer. Do not apply it to the SW cache strategy itself. For complex routing, implement a strict asset manifest validation step in CI\u002FCD to prevent header misapplication.",[14,638,639,640,467],{},"Understanding how edge nodes propagate these directives across global PoPs is essential for scaling. This aligns with broader architectural patterns covered in ",[42,641,643],{"href":642},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",[48,645,647],{"id":646},"common-mistakes","Common Mistakes",[81,649,650,656,665,675,678],{},[84,651,652,653,655],{},"Applying ",[18,654,35],{}," to HTML, JSON, or unversioned API endpoints, causing permanent stale content delivery.",[84,657,658,659,661,662,664],{},"Omitting ",[18,660,39],{}," alongside ",[18,663,35],{},", which defaults to browser heuristics and breaks the directive.",[84,666,667,668,25,671,674],{},"Using ",[18,669,670],{},"no-cache",[18,672,673],{},"must-revalidate"," in the same header string, creating conflicting cache directives.",[84,676,677],{},"Failing to update CDN cache keys to ignore query strings, causing cache fragmentation despite correct headers.",[84,679,680],{},"Deploying hashed assets without verifying the hash matches the build output, leading to 404s on cache purge.",[48,682,684],{"id":683},"faq","FAQ",[14,686,687,693],{},[136,688,689,690,692],{},"Does ",[18,691,68],{}," work if the user clears their browser cache?","\nNo. The directive only applies to resources already stored in the browser's HTTP cache. Once cleared, the browser must fetch the asset from the origin or CDN again. It will respect the directive for subsequent visits.",[14,695,696,704,705,707,708,710,711,713,714,716],{},[136,697,698,699,661,701,703],{},"Can I use ",[18,700,35],{},[18,702,632],{},"?","\nTechnically yes, but it is architecturally redundant. ",[18,706,35],{}," guarantees zero revalidation, while ",[18,709,632],{}," explicitly allows background validation. Use ",[18,712,35],{}," for hashed static assets and ",[18,715,632],{}," for dynamic content.",[14,718,719,724,725,727,728,731,732,734,735,467],{},[136,720,721,722,703],{},"Why does Lighthouse still flag my hashed assets after adding ",[18,723,35],{},"\nLighthouse flags assets if ",[18,726,39],{}," is below ",[18,729,730],{},"31536000",", if the header is malformed, or if a service worker intercepts the request and modifies cache behavior. Verify raw response headers via DevTools and ensure your SW ",[18,733,611],{}," handler does not override ",[18,736,737],{},"Cache-Control",[14,739,740,743],{},[136,741,742],{},"How do I safely purge immutable assets during a deployment rollback?","\nImmutable assets are tied to unique hashes. A rollback simply deploys the previous hash version. Since browsers cache by URL, the old hash remains valid and cached. You do not need to purge immutable assets; revert the build manifest to point to the previous hash.",[745,746,747],"style",{},"html pre.shiki code .sCJTb, html code.shiki .sCJTb{--shiki-default:#FF9492;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sJdzJ, html code.shiki .sJdzJ{--shiki-default:#ADDCFF;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .s3sCt, html code.shiki .s3sCt{--shiki-default:#F0F3F6;--shiki-dark:#F0F3F6;--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 .sGhOu, html code.shiki .sGhOu{--shiki-default:#DBB7FF;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .spFnL, html code.shiki .spFnL{--shiki-default:#FFB757;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .s1o6E, html code.shiki .s1o6E{--shiki-default:#72F088;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold;--shiki-light:#024C1A;--shiki-light-font-weight:bold}html pre.shiki code .s5hCx, html code.shiki .s5hCx{--shiki-default:#91CBFF;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sj_b3, html code.shiki .sj_b3{--shiki-default:#72F088;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":201,"searchDepth":226,"depth":226,"links":749},[750,751,752,753,754,755,756],{"id":50,"depth":226,"text":51},{"id":75,"depth":226,"text":76},{"id":157,"depth":226,"text":158},{"id":518,"depth":226,"text":519},{"id":604,"depth":226,"text":605},{"id":646,"depth":226,"text":647},{"id":683,"depth":226,"text":684},"Modern frontend build pipelines generate content-hashed filenames (e.g., app.8f3a9c.js) to guarantee asset uniqueness across deployments. Despite this, browsers still issue conditional If-None-Match or If-Modified-Since requests on page reloads. These generate unnecessary 304 responses and add measurable network latency.","md",{},true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets",{"title":5,"description":757},"advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002Findex","LSLgb0JNpRy7Z4zX8LiudroCut1tBjQ90PBnhiFldMw",[766,769],{"title":45,"path":767,"stem":768,"children":-1},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained","advanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Findex",{"title":770,"path":771,"stem":772,"children":-1},"Service Worker Caching Strategies: Implementation, Diagnostics & Thresholds","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Findex",1777925998822]