{"id":1555,"date":"2025-11-08T20:42:32","date_gmt":"2025-11-08T17:42:32","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/stop-fighting-your-cache-the-friendly-guide-to-cache-control-immutable-etag-vs-last%e2%80%91modified-and-asset-fingerprinting\/"},"modified":"2025-11-08T20:42:32","modified_gmt":"2025-11-08T17:42:32","slug":"stop-fighting-your-cache-the-friendly-guide-to-cache-control-immutable-etag-vs-last%e2%80%91modified-and-asset-fingerprinting","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/stop-fighting-your-cache-the-friendly-guide-to-cache-control-immutable-etag-vs-last%e2%80%91modified-and-asset-fingerprinting\/","title":{"rendered":"Stop Fighting Your Cache: The Friendly Guide to Cache-Control immutable, ETag vs Last\u2011Modified, and Asset Fingerprinting"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>Ever ship a tiny CSS tweak and watch it stubbornly refuse to show up for half your users? I\u2019ve been there\u2014staring at a header dump at 1 a.m., whispering promises to the CDN gods if they\u2019d just let that new button color through. Caching is hilarious because when it\u2019s right, nobody notices, and when it\u2019s wrong, your support inbox catches fire. The good news: there\u2019s a calm, reliable way to make static files behave. It boils down to three ingredients\u2014strong <strong>Cache-Control<\/strong> with <strong>immutable<\/strong>, choosing between <strong>ETag<\/strong> and <strong>Last-Modified<\/strong> (or using them together wisely), and leaning hard into <strong>asset fingerprinting<\/strong> so cache invalidation becomes a non-event.<\/p>\n<p>I want to walk you through the mental model I wish I\u2019d learned earlier. We\u2019ll chat about how browsers decide to reuse a file, why immutable is the best kind of laziness, how conditional requests actually flow, and how a hash in your filename turns \u201ccache invalidation\u201d from a headache into a shrug. I\u2019ll share a couple of stories, a few copy\u2011pasteable header snippets, and the little pitfalls that have bitten me (so you can skip them). And along the way, I\u2019ll point you to a few pieces where I\u2019ve tackled related performance puzzles like image optimization, HTTP\/2+HTTP\/3, and S3 offloading.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#The_moment_caching_finally_made_sense\"><span class=\"toc_number toc_depth_1\">1<\/span> The moment caching finally made sense<\/a><\/li><li><a href=\"#A_friendly_mental_model_of_web_caching\"><span class=\"toc_number toc_depth_1\">2<\/span> A friendly mental model of web caching<\/a><\/li><li><a href=\"#Cache-Control_and_immutable_the_quiet_superpower\"><span class=\"toc_number toc_depth_1\">3<\/span> Cache-Control and immutable: the quiet superpower<\/a><\/li><li><a href=\"#ETag_vs_Last-Modified_the_check-in_desk\"><span class=\"toc_number toc_depth_1\">4<\/span> ETag vs Last-Modified: the check-in desk<\/a><\/li><li><a href=\"#Asset_fingerprinting_the_stress_antidote\"><span class=\"toc_number toc_depth_1\">5<\/span> Asset fingerprinting: the stress antidote<\/a><\/li><li><a href=\"#Real-world_header_patterns_that_just_work\"><span class=\"toc_number toc_depth_1\">6<\/span> Real-world header patterns that just work<\/a><\/li><li><a href=\"#A_word_on_images_compression_and_why_cache_keys_matter\"><span class=\"toc_number toc_depth_1\">7<\/span> A word on images, compression, and why cache keys matter<\/a><\/li><li><a href=\"#CDNs_invalidation_and_avoiding_the_stale_but_broken_release\"><span class=\"toc_number toc_depth_1\">8<\/span> CDNs, invalidation, and avoiding the \u201cstale but broken\u201d release<\/a><\/li><li><a href=\"#Implementation_notes_for_Nginx_Apache_Node_and_friends\"><span class=\"toc_number toc_depth_1\">9<\/span> Implementation notes for Nginx, Apache, Node, and friends<\/a><\/li><li><a href=\"#Testing_verifying_and_fixing_the_sneaky_stuff\"><span class=\"toc_number toc_depth_1\">10<\/span> Testing, verifying, and fixing the sneaky stuff<\/a><\/li><li><a href=\"#Common_pitfalls_Ive_tripped_over_so_you_dont_have_to\"><span class=\"toc_number toc_depth_1\">11<\/span> Common pitfalls I\u2019ve tripped over (so you don\u2019t have to)<\/a><\/li><li><a href=\"#Putting_it_all_together_a_simple_checklist\"><span class=\"toc_number toc_depth_1\">12<\/span> Putting it all together: a simple checklist<\/a><\/li><li><a href=\"#Wrap-up_make_caching_boring_and_fast\"><span class=\"toc_number toc_depth_1\">13<\/span> Wrap-up: make caching boring (and fast)<\/a><\/li><\/ul><\/div>\n<h2 id=\"section-1\"><span id=\"The_moment_caching_finally_made_sense\">The moment caching finally made sense<\/span><\/h2>\n<p>Years ago, I pushed a redesign and went for aggressive caching\u2014month-long <em>max-age<\/em>, confident swagger, the whole vibe. Then someone changed the logo SVG a day later. Oops. People kept seeing the old logo, and clearing caches felt like chasing ghosts across every browser and ISP. That\u2019s when it clicked: caching isn\u2019t just about setting long expirations. It\u2019s about <strong>versioning<\/strong> what you cache so you never need to \u201crecall\u201d it. If a file might change, the URL must change with it. Once you internalize that, everything else\u2014the headers, the CDNs, the browser gymnastics\u2014becomes dramatically simpler.<\/p>\n<p>Think of your static assets like jars of jam. If the jars are sealed and labeled with a unique batch number, you can keep them in the pantry forever and never worry. If you pour new jam into an old jar with the same label, you\u2019re going to have some confused breakfasts. Asset fingerprinting is just the batch number on the label. Cache-Control: immutable is your permission to stash it on the shelf and forget it.<\/p>\n<h2 id=\"section-2\"><span id=\"A_friendly_mental_model_of_web_caching\">A friendly mental model of web caching<\/span><\/h2>\n<p>Here\u2019s the thing most docs gloss over: there are two big questions the browser asks every time it needs an asset. First, \u201cCan I reuse what I already have without asking the server?\u201d That\u2019s where Cache-Control and freshness come in. Second, \u201cIf I can\u2019t reuse it outright, can I at least quickly check if the server\u2019s copy changed?\u201d That\u2019s where ETag and Last-Modified enter, with conditional requests that are cheap but not free.<\/p>\n<p>So in your ideal flow, a versioned, fingerprinted filename gives the browser permission to hold onto the file for a very long time, <strong>without revalidating<\/strong>. When you ship a new version, the filename changes, and the browser fetches the new file once, then resumes hoarding it guilt-free. That\u2019s the loop. That\u2019s the serenity.<\/p>\n<p>When I explain this to clients, I like to say: \u201cYour HTML is fresh bread; your JS, CSS, and images are canned beans.\u201d The bread gets served fresh\u2014short caching, no immutable, often private or with must-revalidate. The beans get a long shelf life\u20141 year max-age, immutable, public. Keep those roles straight and caching stops being mysterious.<\/p>\n<h2 id=\"section-3\"><span id=\"Cache-Control_and_immutable_the_quiet_superpower\">Cache-Control and immutable: the quiet superpower<\/span><\/h2>\n<p>Let\u2019s talk about the hero that rarely gets top billing: <strong>Cache-Control: immutable<\/strong>. This hint tells the browser, \u201cThis file will never change at this URL. Don\u2019t even re-check it until it\u2019s stale.\u201d It\u2019s a nudge that cuts out a surprising amount of background validation traffic. Even if your <em>max-age<\/em> is long, lots of setups still trigger revalidation under some conditions. Immutable says, \u201cRelax. Save the trip.\u201d<\/p>\n<p>Pair immutable with a generous <em>max-age<\/em> for fingerprinted assets and you get a reliable, fast path. Something like:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Cache-Control: public, max-age=31536000, immutable\n<\/code><\/pre>\n<p>Translated: store this publicly (browser and CDN can both hold it), keep it for a year, and don\u2019t revalidate if you already have it. This setting is perfect for files whose URL carries a content hash: app.4f2c9f1.js, styles.7890ab.css, logo.12ac34.svg. If you don\u2019t have a build pipeline adding hashes yet, we\u2019ll get there\u2014because it changes everything.<\/p>\n<p>HTML is the odd one out. The document itself should usually <em>not<\/em> be immutable, and its <em>max-age<\/em> should be quite short or set for revalidation. You want to be able to ship new HTML quickly to reference your new asset URLs. In practice, that means your index page might carry headers like:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Cache-Control: no-cache\n<\/code><\/pre>\n<p>No-cache doesn\u2019t mean \u201cdon\u2019t store.\u201d It means \u201cstore, but revalidate before using.\u201d That gives you a cheap way to ensure users see the latest HTML while still letting intermediate caches do their job efficiently.<\/p>\n<p>If you like reading the official wording, MDN\u2019s page on cache headers is solid and surprisingly readable. I\u2019ll link it here because it\u2019s handy for quick checks: <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Cache-Control\" rel=\"noopener nofollow\" target=\"_blank\">MDN on Cache-Control directives (including immutable)<\/a>.<\/p>\n<h2 id=\"section-4\"><span id=\"ETag_vs_Last-Modified_the_check-in_desk\">ETag vs Last-Modified: the check-in desk<\/span><\/h2>\n<p>I remember one migration where the site felt zippy locally, but production had this soft lag on every HTML request. Turned out the origin was issuing ETags with weak validators that changed per server node, so the CDN kept revalidating and never getting a clean 304. We fixed it by issuing stable, strong ETags computed from the actual response bytes, and suddenly 304s flowed, latency dropped, and the ops channel went quiet.<\/p>\n<p>Here\u2019s how I boil it down when people ask me to pick: <strong>ETag<\/strong> is a fingerprint of the response, often a hash, and the most precise choice for conditional requests. <strong>Last-Modified<\/strong> is a timestamp of when the file changed on disk. If your deploys occasionally shuffle timestamps without changing content, Last-Modified can turn chatty. If your ETags differ per node or include volatile metadata, they can turn chatty too. The trick is to pick the one you can keep <em>stable<\/em>. In many modern stacks, that\u2019s ETag generated from content, or both ETag and Last-Modified together if you trust your build pipeline.<\/p>\n<p>In practice, an asset might include both:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">ETag: &quot;4f2c9f1-1a2b3c4d&quot;\nLast-Modified: Tue, 22 Oct 2024 19:20:30 GMT\nCache-Control: public, max-age=31536000, immutable\n<\/code><\/pre>\n<p>If that URL is fingerprinted, the browser almost never needs to send If-None-Match or If-Modified-Since until the resource goes stale\u2014hence the speed. If the URL isn\u2019t fingerprinted, strong validators reduce waste, but you still risk the classic \u201cI changed the file but users keep seeing the old version\u201d because the URL didn\u2019t change. Conditional requests are a safety net, not a substitute for versioned filenames.<\/p>\n<p>MDN also has straightforward references for these headers if you want exact semantics. For quick lookup: <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/ETag\" rel=\"noopener nofollow\" target=\"_blank\">ETag header<\/a>.<\/p>\n<h2 id=\"section-5\"><span id=\"Asset_fingerprinting_the_stress_antidote\">Asset fingerprinting: the stress antidote<\/span><\/h2>\n<p>Let\u2019s talk about the piece that makes everything else boring\u2014in a good way. <strong>Asset fingerprinting<\/strong> (or content hashing) means you bake a hash of the file\u2019s contents into the filename or path. Your CSS might become styles.7890ab.css. If you change a single character, the hash changes, and so does the filename. Because the URL is unique, caches can keep the old file forever without hurting you, and new users get the new one on the first visit. No purges, no late-night \u201cwhy is the button still blue\u201d mysteries.<\/p>\n<p>One of my clients moved from timestamp-based names to content hashes, and the payoff was immediate. We swapped their headers to long-lived immutable for JS\/CSS\/images and kept HTML lean. Their deploys got calmer, and even their CDN bills nudged down because revalidation traffic went way down. This is the kind of change that pays rent every month.<\/p>\n<p>If you\u2019re already bundling with tools like Webpack, Rollup, Vite, or Parcel, you\u2019re likely one flag away. Many static site generators and frameworks ship with hashed filenames out of the box. If you\u2019re hand-rolling, I\u2019ve seen simple scripts that output a SHA-1 or MD5 in the filename and rewrite references in HTML templates during build. It doesn\u2019t have to be fancy. It just has to be consistent.<\/p>\n<p>If you\u2019re dealing with big media and want to offload storage and bandwidth, this plays beautifully with object storage and CDNs. I walked through that pattern in detail\u2014signed URLs, timestamps, and cache invalidation\u2014in this practical guide: <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-medyani-s3e-tasiyalim-mi-cdn-imzali-url-ve-onbellek-gecersizlestirme-adim-adim\/\">Offloading WordPress media to S3-compatible storage and doing cache invalidation the right way<\/a>. The same principles apply even if you\u2019re not on WordPress.<\/p>\n<h2 id=\"section-6\"><span id=\"Real-world_header_patterns_that_just_work\">Real-world header patterns that just work<\/span><\/h2>\n<p>Alright, let\u2019s put rubber on the road. Below are simple header snippets that have served me well. They\u2019re not dogma\u2014tune them to your stack\u2014but they\u2019ll get you 90% there without drama.<\/p>\n<p>For fingerprinted static assets (JS, CSS, images, fonts):<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Cache-Control: public, max-age=31536000, immutable\nETag: &quot;&lt;content-hash&gt;&quot;\nLast-Modified: &lt;build-time&gt;\n<\/code><\/pre>\n<p>For HTML documents and API responses that must reflect current state:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Cache-Control: no-cache\nETag: &quot;&lt;content-hash-or-version&gt;&quot;\n<\/code><\/pre>\n<p>For static HTML that updates occasionally but can tolerate short freshness windows:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Cache-Control: public, max-age=60, must-revalidate\n<\/code><\/pre>\n<p>In Nginx, you can wire this up quickly. I\u2019ve covered broader performance tunings like TLS and Brotli elsewhere, but just the cache bits might look like this:<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">location ~* .(?:js|css|png|jpg|jpeg|gif|svg|webp|avif|woff2?)$ {\n    add_header Cache-Control &quot;public, max-age=31536000, immutable&quot;;\n}\n\nlocation ~* .(?:html)$ {\n    add_header Cache-Control &quot;no-cache&quot;;\n}\n<\/code><\/pre>\n<p>When you\u2019re rolling with a CDN in front, keep origin and CDN headers aligned in spirit. If you\u2019re going to cache a year at the browser, make sure your CDN tiered caching or edge TTLs match or exceed your needs, and avoid revalidations unless you truly need them. I\u2019ve dug into the protocol side of speed\u2014multiplexing, QUIC, connection reuse\u2014in this guide: <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-ve-cloudflareda-http-2-ve-http-3-quic-nasil-etkinlestirilir-wordpress-icin-uctan-uca-kurulum-ve-test-rehberi\/\">Enabling HTTP\/2 and HTTP\/3 on Nginx + Cloudflare, end-to-end<\/a>. Faster pipes magnify good caching.<\/p>\n<h2 id=\"section-7\"><span id=\"A_word_on_images_compression_and_why_cache_keys_matter\">A word on images, compression, and why cache keys matter<\/span><\/h2>\n<p>Images are the heavyweight champs of your payload, and they deserve special love. Once you start serving AVIF\/WebP versions, you\u2019ll likely vary responses by Accept headers or query params. This is where cache keys and smart variations make a difference. I\u2019ve shared how I handle this in detail\u2014format negotiation, origin shield, and keeping your CDN bill friendly\u2014in this playbook: <a href=\"https:\/\/www.dchost.com\/blog\/en\/goruntu-optimizasyonu-boru-hatti-nasil-kurulur-avif-webp-origin-shield-ve-akilli-cache-key-ile-cdn-faturaniza-nefes-aldirin\/\">Building an image optimization pipeline with AVIF\/WebP and smarter cache keys<\/a>.<\/p>\n<p>Meanwhile, on the compression side, Brotli can squeeze more out of your JS and CSS than gzip, especially with static precompression at build time. I paired it with modern TLS in one of my favorite practical guides here: <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginxte-tls-1-3-ocsp-stapling-ve-brotli-nasil-kurulur-hizli-ve-guvenli-httpsnin-sicacik-rehberi\/\">TLS 1.3 and Brotli on Nginx\u2014the speed-and-security tune-up<\/a>. Precompressed static files plus immutable caching is a great combo because you\u2019re paying the compression cost once at build, not per request at runtime.<\/p>\n<h2 id=\"section-8\"><span id=\"CDNs_invalidation_and_avoiding_the_stale_but_broken_release\">CDNs, invalidation, and avoiding the \u201cstale but broken\u201d release<\/span><\/h2>\n<p>I\u2019ve learned to treat invalidation as a last resort. If you fingerprint your assets, you seldom need to purge anything other than the document HTML. In a sane pipeline, your release order is: ship assets with hashes, then ship HTML that references them. That way, if a page loads in the middle of your deploy, the worst that happens is someone gets the previous HTML for a minute, which still references assets that exist. If you do this backward\u2014HTML first, then assets\u2014you risk 404s during the window, and a lot of frantic refreshes.<\/p>\n<p>Immutable is a trust contract, so don\u2019t cheat it. If you ever push new content to an old filename, you\u2019re going to confuse caches across every layer and device. That\u2019s when you start hunting down weird ghost responses and muttering about proton storms. If you really must override, then drop immutable and rely on revalidation with short max-age. But know that you\u2019re choosing a noisier path.<\/p>\n<p>For big media libraries, offloading to object storage with versioned object keys and signed URLs gives you rock-solid cache behavior at scale. Again, I\u2019ve broken down that flow with real steps and gotchas in the piece I linked earlier on S3-compatible storage. If you need to purge, purge explicitly and narrowly. Don\u2019t make \u201cnuke everything\u201d your weekly ritual.<\/p>\n<h2 id=\"section-9\"><span id=\"Implementation_notes_for_Nginx_Apache_Node_and_friends\">Implementation notes for Nginx, Apache, Node, and friends<\/span><\/h2>\n<p>Every stack handles headers a bit differently, but your strategy doesn\u2019t change. You want to set headers at build or at the edge, and you want to make them predictable.<\/p>\n<p>In Nginx, I prefer location-based rules and, for precompressed assets, serving .br\/.gz with correct Vary and Content-Encoding. In Apache, mod_headers gets you there, and you can set ETags carefully (or disable the default file system ETag if it\u2019s too chatty). In Node, Express or Fastify can set headers per route; if you pre-build your fingerprinted assets, you can serve them from a static middleware with a fixed header set.<\/p>\n<p>Wherever possible, I like to generate ETags from file content during the build and inject them as part of the deployment. That way, every node in a cluster returns the same validator, and CDNs pass through consistent 304s when needed. If that\u2019s not practical, rely on the filename hash and keep conditional requests minimal by leaning on immutable. It\u2019s okay to be pragmatic here.<\/p>\n<p>If you\u2019re containerized or orchestrating multiple services, your release flow matters as much as your headers. I\u2019ve shared no-drama deployment patterns you can adapt\u2014zero-downtime swaps, health checks, the works\u2014in this friendly playbook: <a href=\"https:\/\/www.dchost.com\/blog\/en\/vpse-sifir-kesinti-ci-cd-nasil-kurulur-rsync-sembolik-surumler-ve-systemd-ile-sicacik-bir-yolculuk\/\">Zero-downtime CI\/CD to a VPS using rsync and symlinked releases<\/a>. Clean deploys make caching predictable.<\/p>\n<h2 id=\"section-10\"><span id=\"Testing_verifying_and_fixing_the_sneaky_stuff\">Testing, verifying, and fixing the sneaky stuff<\/span><\/h2>\n<p>When I test caching, I like to wear three hats. First, I\u2019m the browser: I open DevTools, look at the Network panel, and check \u201cfrom disk cache,\u201d \u201cfrom memory cache,\u201d and headers on first and repeat views. Second, I\u2019m the CDN: I curl with -I and see what headers make it to the edge, what the origin sets, and whether 304s are happening. Third, I\u2019m the user on a mediocre connection: I throttle the network and feel the difference between a clean cache and a chatty one. The qualitative check matters more than we admit.<\/p>\n<p>Lighthouse and WebPageTest are helpful sanity checks. Lighthouse\u2019s long cache TTL audit is a good nudge if something slips. If you want a quick reference, Google\u2019s guide on long cache TTLs is a decent companion: <a href=\"https:\/\/web.dev\/uses-long-cache-ttl\/\" rel=\"noopener nofollow\" target=\"_blank\">why long cache TTLs improve performance<\/a>. But don\u2019t optimize for the score\u2014optimize for the <em>flow<\/em>: HTML quick to update, assets rock-solid and lazy to change.<\/p>\n<p>One more thing: keep an eye on <em>Vary<\/em> headers. If you vary on headers that change often (like cookies) for your static assets, you\u2019ll explode your cache and force unnecessary misses. For assets, you usually want no cookies and minimal Vary, except for content negotiation cases like images where Accept matters. It\u2019s easy to accidentally let a framework leak cookies into asset routes\u2014double-check that.<\/p>\n<h2 id=\"section-11\"><span id=\"Common_pitfalls_Ive_tripped_over_so_you_dont_have_to\">Common pitfalls I\u2019ve tripped over (so you don\u2019t have to)<\/span><\/h2>\n<p>I\u2019ll confess a few wounds. First, accidentally serving unversioned CSS with a 1-year immutable policy. Users were stuck until they hard-refreshed. Solution: never ship long-lived policies unless the filename is fingerprinted. Second, per-node ETags that differed because the build machine added a timestamp footer. CDNs revalidated forever. Solution: compute ETags from file content only, or strip out the volatile bits. Third, mixing gzip on the fly with precompressed assets led to mismatched Content-Length on edge nodes. Solution: serve precompressed consistently and let the CDN handle negotiation cleanly.<\/p>\n<p>There\u2019s also the \u201cdouble cache\u201d problem. The CDN says 200 but from cache, while the browser still revalidates with the CDN because of missing immutable or short freshness. It looks fast in logs but feels slower on the user\u2019s device. Solution: give the browser permission to reuse aggressively for assets. Prefer immutable where the URL guarantees stability.<\/p>\n<h2 id=\"section-12\"><span id=\"Putting_it_all_together_a_simple_checklist\">Putting it all together: a simple checklist<\/span><\/h2>\n<p>Let me distill the whole story into a mental checklist you can run during your next deploy. First, are your CSS\/JS\/images\/fonts fingerprinted in their filenames? If not, fix that before you touch headers. Second, do fingerprinted assets ship with Cache-Control: public, max-age=31536000, immutable? Third, is your HTML cached conservatively (no-cache or a very short max-age plus must-revalidate)? Fourth, are your ETags stable across nodes and derived from content (or otherwise predictable)? Fifth, do you avoid varying static assets on cookies or other noisy headers? Sixth, can you release assets first, then HTML, to avoid broken references during deploys?<\/p>\n<p>Run through that list and you\u2019ll dodge 90% of cache pain. For the remaining 10%, you\u2019ll have clear levers to pull and logs that make sense.<\/p>\n<h2 id=\"section-13\"><span id=\"Wrap-up_make_caching_boring_and_fast\">Wrap-up: make caching boring (and fast)<\/span><\/h2>\n<p>Let\u2019s circle back to that late-night logo that wouldn\u2019t update. The fix wasn\u2019t a clever purge command or a new CDN vendor. It was adopting asset fingerprinting and giving browsers explicit permission with Cache-Control: immutable for anything that carried a content hash. After that, ETag and Last-Modified stopped being band-aids and became what they\u2019re meant to be: quiet validators for the few cases where you can\u2019t cache forever.<\/p>\n<p>If you\u2019re just getting started, begin with the build: add hashes to filenames and wire up your HTML to reference them. Then set your headers\u2014long-lived and immutable for assets, cautious for HTML. Finally, test with DevTools, curl from the edge, and feel the flow on a throttled connection. If you want to go deeper on parallel performance tweaks, I\u2019ve also shared friendly walkthroughs on <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-ve-cloudflareda-http-2-ve-http-3-quic-nasil-etkinlestirilir-wordpress-icin-uctan-uca-kurulum-ve-test-rehberi\/\">HTTP\/2 and HTTP\/3<\/a>, <a href=\"https:\/\/www.dchost.com\/blog\/en\/goruntu-optimizasyonu-boru-hatti-nasil-kurulur-avif-webp-origin-shield-ve-akilli-cache-key-ile-cdn-faturaniza-nefes-aldirin\/\">modern image pipelines with smart cache keys<\/a>, and <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-medyani-s3e-tasiyalim-mi-cdn-imzali-url-ve-onbellek-gecersizlestirme-adim-adim\/\">offloading media to S3-compatible storage with clean invalidation<\/a>. It all stacks together.<\/p>\n<p>Make caching boring and predictable, and your site feels fast without trying. Your deploys get quieter, your logs get friendlier, and your support inbox stops hearing about \u201cmissing buttons.\u201d Hope this was helpful! If you\u2019ve got a caching war story, I\u2019d love to hear it next time. Until then, ship calmly.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Ever ship a tiny CSS tweak and watch it stubbornly refuse to show up for half your users? I\u2019ve been there\u2014staring at a header dump at 1 a.m., whispering promises to the CDN gods if they\u2019d just let that new button color through. Caching is hilarious because when it\u2019s right, nobody notices, and when it\u2019s [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1556,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1555","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1555","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=1555"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1555\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/1556"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=1555"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=1555"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=1555"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}