The Invisible Layer: Mastering HTTP Caching (Part 2)

Published: (February 16, 2026 at 05:45 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

If you’ve ever screamed, “I updated the data in the database, but the user is still seeing the old version!” while frantically hard‑refreshing your browser, you have met the HTTP cache.

When you call fetch('/api/user'), the browser doesn’t immediately go to the internet. It runs through a strict checklist:

  1. Memory/Disk Check – Do I have a copy of this locally?
  2. Expiration Check – If yes, is it fresh (based on max-age)?
  3. Short Circuit – If it is fresh, the browser returns the data immediately and never talks to the server.

These steps are controlled by the Cache‑Control header.

Cache‑Control

The most important header for web performance. It tells the browser exactly how to behave.

max-age (The Timer)

Cache-Control: max-age=3600

The data is fresh for 1 hour (3600 seconds). The browser won’t ask the server again until that time expires.

Trap: If you deploy a critical bug fix 5 minutes later, users with the cached version won’t see it for another 55 minutes.

no-cache vs no-store (The Great Confusion)

DirectiveMeaningTypical Use
no-storeNever save this response.Sensitive data (e.g., banking info) or data that changes every millisecond.
no-cacheSave it, but revalidate with the server before using it.When you need the latest version on every request.

Note: no-cache forces the browser to ask the server, “Is this version still good?” on each request.

Reference: MDN Web Docs – Cache‑Control

ETags and Last‑Modified (304 Not Modified)

When dealing with large resources (e.g., a 2 MB list of 5,000 products), you don’t want to download the whole file every time.

  1. First request: Server sends the data plus an ETag (a unique hash/fingerprint).
  2. Subsequent request: Browser sends the ETag back in If-None-Match.
  3. Server response: If the hash matches, it returns 304 Not Modified.

Result: The browser reuses the cached version, saving bandwidth and time.

Stale‑While‑Revalidate

Cache-Control: max-age=60, stale-while-revalidate=600
  • Fresh (≤ 60 s): Serve instantly from cache.
  • Stale (60 s – 600 s): Serve the stale response immediately, then fetch a fresh copy in the background and update the cache.

This eliminates loading spinners while keeping data reasonably fresh.

Deep dive: web.dev – Love your cache (stale‑while‑revalidate)

Why This Matters for React Developers

You might think, “I’m a frontend dev; I don’t configure server headers!”
The reality is you can’t fix with JavaScript what you broke with HTTP.

  • If an API sends Cache-Control: no-store, libraries like React Query (TanStack Query) can’t maintain an effective client‑side cache because the browser refuses to store responses.
  • If an API sends max-age=31536000 (1 year) for a user profile, users will never see profile updates until the cache expires.

Action: Inspect response headers in Chrome DevTools → Network tab. The browser’s “receipt” system (conditional requests) will let you know when a resource is unchanged (304) and avoid unnecessary downloads.

User Experience Benefits

  • Stale‑while‑revalidate shows old data instantly while fetching fresh data in the background, removing the need for loading spinners.
  • Proper cache headers reduce latency, bandwidth usage, and improve perceived performance.

Deep Dive Resources

  • MDN HTTP Caching Guide – Comprehensive manual on how browsers handle storage.
  • Google Web.dev Guide – Practical guide on configuring headers for performance and Lighthouse scores.
  • Cloudflare CDN Learning Center – Understanding how edge caching interacts with browser caching.

What’s Next?

Now that we understand the invisible layer, we can move to the Application Layer. In Part 3, we’ll explore React Query (TanStack Query) and see how to implement an efficient caching system with it.

See you in Part 3.

0 views
Back to Blog

Related posts

Read more »