DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on

A Practical Guide to Browser Caching for Web Apps

Introduction
When your app feels snappy, users engage more and your infrastructure costs less. Browser caching is one of the simplest, highest-impact optimizations you can make. The trick is balancing speed with freshness: you want returning users to load instantly, but you also need them to get new code right after you deploy. This guide explains a proven, low-maintenance caching strategy you can adopt for any modern web app.

What browser caching is
A browser stores copies of files it downloads (HTML, JavaScript, CSS, images, fonts) on the user’s device. On the next visit, it can reuse those files instead of downloading them again. The result is faster pages, lower bandwidth, fewer servers needed, and a better user experience.

Where caching happens

  • Browser cache: Local to the user’s device (fastest path for repeat visits).
  • CDN or proxy cache: Copies at edge servers closer to users (reduces origin load and latency).
  • Service worker cache (optional): App-controlled caching logic for offline and advanced update strategies.

Why caching matters

  • Performance: Returning visitors see dramatic speed gains.
  • Cost: Fewer bytes leave your origin and APIs.
  • Reliability: Less load on your servers during traffic spikes and incidents.

The main challenge: staying fresh after a deployment
If you cache aggressively, users can get stuck on old files. The goal is to ensure that when you deploy a new build, users automatically receive the correct new files without you purging all caches or asking users to hard refresh.

The golden pattern (simple and reliable)

  • HTML revalidates on every navigation.
  • Static assets (JS, CSS, images) get long cache lifetimes, but their filenames change when their content changes.
  • APIs revalidate frequently using ETag or Last-Modified.

How to implement it

1) Content-hashed filenames for static assets
Use build tools that produce filenames based on the content. Examples: main.4f3c1.js, styles.a9b2.css, logo.8d12.png. The hash changes only when the file’s content changes. After a new build, browsers fetch only the changed files (new names) and reuse unchanged ones.

2) Cache-Control headers

  • HTML: Cache-Control: no-cache, must-revalidate (or max-age=0, must-revalidate). This tells the browser and CDN to check with the server before using a cached copy. With ETag or Last-Modified, the check is cheap and fast.
  • Hashed assets (JS/CSS/images/fonts): Cache-Control: public, max-age=31536000, immutable. Long-lived because the URL is unique to the content.
  • APIs: Cache-Control: no-cache (or a short max-age with must-revalidate) and include ETag or Last-Modified so clients receive quick 304 Not Modified responses when data hasn’t changed.

3) Deployment order

  • Upload new hashed assets first.
  • Then publish the updated HTML that references those new asset filenames.
  • Optionally invalidate CDN cache for HTML routes so the updated entry point propagates quickly.

When to use caching

  • Production: Always. It’s a foundational performance practice.
  • Development: Keep caching minimal to avoid confusion (e.g., disable cache in DevTools or use short max-age).
  • Private or sensitive content: Use stricter headers such as Cache-Control: no-store for confidential pages or data.

What “no-cache” actually means
No-cache does not mean “never store.” It means “revalidate before using the cached copy.” With ETag or Last-Modified, revalidation usually returns a small 304 Not Modified and the browser uses its local copy, which is fast and efficient.

Example user flow after a deploy

  • You deploy a build:
    • main.js changes → becomes main.newhash.js
    • styles.css unchanged → remains styles.samehash.css
    • index.html is updated to reference main.newhash.js and styles.samehash.css
  • A user visits:
    • Browser revalidates index.html and gets the updated HTML.
    • It downloads main.newhash.js (new URL).
    • It reuses cached styles.samehash.css (same URL).
    • Result: only changed files are fetched; unchanged files load instantly.

Configuration examples (conceptual)

Nginx

  • location = /index.html { add_header Cache-Control "no-cache, must-revalidate"; }
  • location ~* .(js|css|png|jpg|jpeg|gif|svg|woff2)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }

Apache (.htaccess)

  • Header set Cache-Control "no-cache, must-revalidate"
  • Header set Cache-Control "public, max-age=31536000, immutable"

Node/Express

  • app.use(express.static("dist", { setHeaders: (res, path) => { if (path.endsWith(".html")) res.setHeader("Cache-Control", "no-cache, must-revalidate"); else res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); }}));

Framework tips

  • React (Vite/Cra), Angular, Vue CLI, Next.js, Nuxt: Production builds typically create content-hashed filenames automatically. Verify in your dist/build output that JS/CSS include hashes. Serve these assets with long-lived immutable caching.
  • SSR frameworks (Next.js, Nuxt): Let the framework manage asset hashing. Ensure HTML responses (SSR) have revalidation headers or a short CDN TTL with must-revalidate. Dynamic pages should emit ETag or Last-Modified if feasible.
  • Single Page Apps: Always revalidate index.html. For deep links, serve index.html for app routes with the same headers.

CDN best practices

  • Let your origin send the headers above; most CDNs respect them.
  • Invalidate or purge HTML routes after deployment so the updated entry point becomes visible quickly.
  • With hashed assets, you rarely need to purge JS/CSS because new builds use new filenames.
  • Consider a short CDN TTL for HTML (for example, 60–300 seconds) as a safety net if purges are missed.
  • Keep older hashed assets on the CDN for a while (and at origin) to avoid 404s for users who still reference previous build assets and to support rollbacks.

APIs and data freshness

  • Use ETag or Last-Modified with Cache-Control: no-cache or a short max-age and must-revalidate. This avoids stale data and keeps bandwidth low via 304s.
  • For highly dynamic or sensitive responses that must never be reused, use Cache-Control: no-store.

Service workers (optional, advanced)

  • Service workers allow you to script caching and offline behavior.
  • Version your caches (for example, app-cache-v42) and precache assets on install for predictable offline behavior.
  • On each deploy, publish a new service worker. Decide your update UX:
    • Prompt users to refresh when an update is available (good control and clarity).
    • Or auto-activate with self.skipWaiting() and clients.claim() (faster, but consider UX trade-offs).
  • Do not let the service worker serve stale HTML forever. Use network-first or stale-while-revalidate for HTML so updates are discovered promptly.

How to force revalidation

  • For yourself: Hard refresh (Ctrl/Cmd+Shift+R) or enable “Disable cache” in DevTools.
  • For all users:
    • Keep HTML as no-cache, must-revalidate and return ETag or Last-Modified.
    • Invalidate CDN cache for HTML routes immediately after deploy.
    • In emergencies, temporarily set Cache-Control: no-store on HTML to force a refresh, then revert.

How to verify your setup

  • Browser DevTools → Network:
    • index.html should show 200 or 304 after reload (not “from cache”), indicating revalidation.
    • Hashed JS/CSS should typically show “from disk cache” or “from memory cache” between deployments.
  • curl checks:

Common pitfalls

  • Skipping content hashing: Leads to stale files and complex purges. Always use hashed filenames.
  • Query-string cache busting (file.js?v=123): Some caches ignore query params. Prefer hashed filenames.
  • Long-lived caching for HTML: Users won’t see new builds. Keep HTML revalidated.
  • Removing old assets immediately: Users with older HTML may still request old hashed files. Keep previous builds’ assets available for a safe window.
  • Service worker traps: A service worker that serves stale HTML indefinitely breaks updates. Ensure HTML revalidates and that you have a clear update strategy.

Security and privacy notes

  • Do not cache sensitive or private data. Use Cache-Control: no-store on such responses.
  • If you adopt third-party CDNs, plugins, or service worker libraries, ensure they meet your organization’s security and compliance requirements. At Oracle, verify alignment with internal guidelines before using external tools.

A simple deployment checklist

  • Build outputs include content-hashed filenames for all static assets.
  • HTML responses include Cache-Control: no-cache, must-revalidate and ETag or Last-Modified.
  • Static assets use Cache-Control: public, max-age=31536000, immutable.
  • Upload new hashed assets first, then publish the updated HTML.
  • Invalidate CDN cache for HTML routes after deployment (recommended).
  • Keep the last few builds’ assets available for rollbacks and late-returning users.
  • If using a service worker, bump the version and implement an update prompt or auto-activation strategy.

FAQ

  • Will users always get the latest build? Yes. HTML revalidates and points to new hashed assets; changed assets download, unchanged assets are reused from cache.
  • What if only JS changed? The HTML changes the script src to the new hash and revalidation picks it up automatically.
  • Do I need users to hard refresh? Not with hashing plus proper headers.
  • Is “no-cache” slow? No. With ETag or Last-Modified, the browser typically gets a quick 304 and reuses its local copy.
  • Can I skip CDN purges? Usually yes for assets due to hashing. Purge HTML for immediate propagation.

Closing thoughts
This pattern—revalidated HTML, hashed assets with long-lived immutable caching, and APIs with validators—provides fast loads and safe updates with minimal operational overhead. Start with the basics above, verify with your browser’s Network panel, and iterate as your app grows. If you plan to use third-party tools or CDNs, confirm they comply with your organization’s security and privacy standards. At Oracle, please verify alignment with internal guidelines before adopting external tools.

Top comments (0)