{"id":"kycyb6qf9poa8kk","title":"Service Worker Caching Mistakes on Marketing Sites","slug":"service-worker-caching-mistakes","summary":"Blanket cache-first strategies for marketing sites backfire by serving stale content and broken layouts that the browser refuses to update.  To fix this,…","imageUrl":"https://briancrabtree.me/images/journal-service-worker-caching-mistakes.webp","category":"JavaScript","date":"2026-04-09T18:00:00.000Z","featured":false,"likes":28,"author":"Brian Crabtree","content":"<h2>The Lure of Offline First and its pitfalls</h2>\n\n<p>Service workers, with promises of offline access and lightning-fast loads, tempt any website, especially a marketing site. I envisioned a full PWA experience, believing every page could benefit from robust caching. My enthusiasm, however, overlooked potential caching mistakes.</p>\n\n<p>My initial approach: cache everything. HTML, CSS, JS, images – all designed for \"cache-first.\" This seemed logical, ensuring immediate content delivery on repeat visits. I prioritized speed in my \"PWA marketing site cache\" implementation.</p>\n\n<p>Reality struck after the first content update. Users reported old blog posts and outdated styling. Our deployment pipeline struggled, battling stale content. Speed was overshadowed by broken user experiences and fighting invisible content.</p>\n\n<pre><code>// Avoid caching HTML shell forever — version your SW\nself.addEventListener('activate', (e) =&gt; {\n  e.waitUntil(caches.keys().then((keys) =&gt; Promise.all(keys.map((k) =&gt; caches.delete(k)))));\n});</code></pre>\n\n<h2>The Stale Content Trap</h2>\n\n<p>The biggest issue became the \"stale content trap.\" I'd update a post, but repeat visitors saw the old version. It felt like an endless battle against phantom data, hindering fresh content. This wasn't the reliable \"PWA marketing site cache\" experience.</p>\n\n<p>My service worker aggressively cached HTML documents using a cache-first strategy. If in cache, the browser never revalidated. Great for offline, but detrimental for frequently updated content, effectively bypassing server changes.</p>\n\n<p>Explaining to a client why their latest announcement wasn't live was tough. \"Clear your cache\" isn't a solution for a public site. Our goal was seamless content, not burdening users, revealing a significant flaw.</p>\n\n<h2>Versioning Is Not Just for Code</h2>\n\n<p>Server updates alone weren't enough. The service worker needed to detect changes. My build process lacked proper asset versioning for cached resources, treating modified files as identical if URLs didn't change, preventing new versions.</p>\n\n<p>For JavaScript and CSS, versioning appends a unique content hash to filenames, like `app.1a2b3c.js`. When content changes, the hash changes, creating a new URL. The service worker recognizes it as new, forcing a fresh download.</p>\n\n<p>HTML presents a challenge; you can't hash the file if the URL must remain constant. For dynamic HTML, a network-first or stale-while-revalidate strategy is superior, ensuring freshness without breaking links. More in <a href=\"/journal/cloudflare-cache-stale-assets-after-deploy/\">Cloudflare Cache and Stale Assets After Deploy</a>.</p>\n\n<h2>Cache Strategies and Their Tradeoffs</h2>\n\n<p>Choosing the right caching strategy is critical. Cache-first is for immutable, versioned assets. Network-first ensures freshest content, falling back to cache if offline. Stale-while-revalidate balances speed and freshness, showing cached then updating in background.</p>\n\n<p>For my marketing site, HTML documents use network-first or stale-while-revalidate. Versioned JS and CSS, however, still use cache-first. This hybrid approach ensures latest page content from network, while static assets benefit from aggressive caching.</p>\n\n<p>The key insight was asset-specific strategies. Static images or versioned scripts can be aggressively cached. Main HTML documents require a conservative approach. This prevents stale content, prioritizing freshness where it matters most.</p>\n\n<h2>Debugging Cache Nightmares</h2>\n\n<p>Debugging service workers is tricky. Chrome DevTools offer insights, but understanding real user experiences is harder. Most users don't grasp service worker lifecycles or how to force updates.</p>\n\n<p>I spent hours manually clearing caches and unregistering service workers. This was tedious. Understanding the service worker lifecycle, especially `updatefound` and `activate` events, became crucial for diagnosing why new content wasn't showing.</p>\n\n<p>The \"waiting\" state was frustrating. A new service worker might download but wouldn't activate until all old tabs closed. This often meant users needed to close and reopen their browser for updates to take effect.</p>\n\n<h2>The Performance vs Freshness Dilemma</h2>\n\n<p>My initial drive for service workers was pure performance. Instant loads and offline capabilities are powerful. But achieving those gains without sacrificing content freshness requires a delicate balance on a marketing site.</p>\n\n<p>On a marketing site, showing the latest information is critical. An outdated hero banner or pricing table directly impacts business. Performance benefits diminish if content is incorrect or misleading, eroding user trust.</p>\n\n<p>Lesson learned: for frequently updated content like blog posts, prioritize freshness. For static assets, leverage aggressive caching. Always weigh the business impact of stale content before applying a global cache-first strategy.</p>\n\n<h2>Refining the Strategy</h2>\n\n<p>Moving forward, I've simplified my service worker strategy for marketing sites. HTML documents are typically network-first for immediate freshness. Critical, versioned assets like core CSS and JS bundles now strictly use cache-first with robust versioning.</p>\n\n<p>This refined approach reduces the chance of users seeing old content while still leveraging performance. Clear cache invalidation for every deployment is paramount. It’s about being pragmatic, not dogmatic, about offline features.</p>\n\n<p>Understanding these tradeoffs has made my deployments smoother and user experience more reliable. For specifics on my setup, the architecture is detailed in <a href=\"/journal/how-this-site-is-built/\">How This Site Is Built (Reference Stack)</a>. This transparency helps prevent future missteps.</p>","tags":["service-worker","pwa","caching"],"views":82}