Configuring JavaScript caches for better performance
JavaScript caching seems straightforward until you're debugging why users are stuck on stale code after a critical hotfix or why your CDN bill doubled after implementing immutable assets. The mechanics of browser caches, CDN edge caches, and service workers interact in ways that can either dramatically improve your Core Web Vitals or create deployment nightmares.
The fundamental tradeoff is between cache hit rates and deployment velocity. Setting Cache-Control max-age to 31536000 (one year) on your bundle.js gives you excellent cache performance but means users won't see updates without a hard refresh. The standard solution is content hashing in filenames, but implementation details matter significantly. Webpack's contenthash and Vite's hash generation create unique filenames per build, letting you serve assets with immutable headers while your HTML entry point uses no-cache or max-age=300. This works well until you realize your CDN is caching the HTML at edge locations with default TTLs, creating a two-tier staleness problem.
CDN configuration deserves more attention than it typically gets. Setting Cache-Control: public, max-age=31536000, immutable on hashed assets tells both browsers and CDNs to cache aggressively. The immutable directive is particularly valuable because it prevents conditional revalidation requests that add latency even when content hasn't changed. For your HTML entry points, Cache-Control: no-cache, must-revalidate forces revalidation but allows CDNs to serve stale content while revalidating in the background with stale-while-revalidate. A typical configuration might be max-age=0, s-maxage=300, stale-while-revalidate=86400 for HTML, giving users fast responses while ensuring they get fresh content within five minutes.
Service workers add another caching layer that can bypass both browser and CDN caches entirely. Workbox's strategies provide good defaults, but the CacheFirst strategy commonly used for JavaScript assets can cause the same staleness issues you just solved with content hashing. The StaleWhileRevalidate strategy is often better for application code because it serves cached content immediately while fetching updates in the background. You need to implement proper cache versioning though, typically by including a build hash in your cache name and cleaning up old caches in the activate event.
The performance impact shows up clearly in Core Web Vitals. Proper caching directly improves Largest Contentful Paint by reducing JavaScript fetch times, and First Input Delay benefits from faster script execution when code is cached. Real user monitoring data from a recent migration showed LCP improvements of 400-600ms when moving from no-cache on all assets to properly configured immutable caching with content hashing. The catch is that you need robust monitoring to detect cache-related issues. Track your CDN hit rates by content type, monitor service worker update latency, and set up alerts for unexpected cache misses.
The biggest operational mistake is not having a cache busting strategy for emergencies. When you need to force-invalidate cached assets, relying on CDN purge APIs alone isn't enough because browser caches and service workers won't know to refetch. Including a version query parameter that you can increment (?v=2) in your HTML's script tags gives you an emergency escape hatch, though it breaks immutable caching benefits. Better is maintaining a kill switch that can inject a service worker update check or modify cache headers dynamically, but this requires planning before the incident happens.