Browser Cache

The browser cache is the set of copies a browser keeps of files it has already downloaded: HTML pages, scripts, stylesheets, images, fonts and some API responses. When a file is needed again and the stored copy may still be used, the browser takes the copy instead of downloading the file again. Return visits get faster, and a returning user can see something different from a first-time visitor.

Example: a shop releases a green "Pay now" button at 10:00. A customer who visited yesterday opens the checkout at 11:00 and still sees the old blue button, because the browser reused yesterday's stylesheet without asking the server. The release works; it simply has not reached that customer.

Why it matters

Stale content is a common cause of "it works for me" after a release: the tester checked with a clean browser while customers kept old files. The effects range from an old logo to serious failures: new HTML running with old JavaScript that sends requests the server no longer accepts, or an account page that is still visible after logout.

Caching is something the team sets up. Response headers, build settings and service worker code decide what is stored and for how long; when a response has no caching headers, the browser may even guess a lifetime. A wrong or missing setting is a defect like any other. If you only ever test with an empty cache, you never see what most of your users see.

Memory cache, disk cache and service workers

In the lesson on how browsers work you saw Chrome mark Network rows as (memory cache) or (disk cache). The memory cache keeps files the open page has already used; it is fast and short-lived, and does not survive closing the tab. The disk cache is the browser's HTTP cache, a private cache in the terms of RFC 9111: it survives restarts, and the response headers decide what it may store and for how long.

A fresh copy, for example one still within its Cache-Control: max-age lifetime, is used without contacting the server. A stale copy is normally revalidated: the browser sends the copy's version tag (ETag) in If-None-Match, or its date (Last-Modified) in If-Modified-Since, and the server answers 304 Not Modified (keep the copy) or 200 with a new version. no-cache means "store, but revalidate before each use"; no-store means "do not store". The lesson on HTTP caching covers these headers in depth.

A service worker is a script that a site registers in the browser. It sits between the page and the network and can answer requests from its own store, Cache Storage, even when the HTTP cache is empty. By default a new version installs in the background and waits until every tab using the old one is closed, so a normal reload does not switch versions.

A request goes to the service worker if one controls the page; otherwise a fresh stored copy is used without asking the server, a stale one is revalidated, and only a miss is downloaded. Conceptual model
Question 1: Does a service worker control the page?Check Application > Service workers
  • Answer: Yes

    Outcome: The service worker answersIts code chooses Cache Storage or the network, so it can serve an old version(ServiceWorker)
  • Answer: No

    Question 2: Does the browser hold a stored copy?In the memory cache or the disk cache
    • Answer: No

      Outcome: Miss: downloaded from the serverThen stored if the response headers allow it200
    • Answer: Yes

      Question 3: Is the copy still fresh?Within its max-age and not marked no-cache
      • Answer: Yes

        Outcome: Hit: used without asking the serverShown as (memory cache) or (disk cache); a deploy at the same URL does not reach it
      • Answer: No

        Outcome: Revalidated with the server304: the copy is kept; 200: a new version replaces itIf-None-Match

Conceptual model: browsers differ in details; Chrome, for example, checks its short-lived memory cache even before the service worker. A hard reload skips the stored copies and the service worker for one load and deletes nothing.

  • Hard reload (Ctrl+Shift+R, or Cmd+Shift+R on a Mac): requests the page and its files again and skips the service worker, for this one load. It deletes nothing, and a normal reload may still reuse fresh copies.
  • Empty Cache and Hard Reload (Chrome, right-click the reload button while DevTools is open): empties the cache, then hard-reloads.
  • Disable cache (Network panel): no HTTP cache while DevTools is open, but a service worker can still answer.
  • Application panel: Service workers shows the active and any waiting version, with Bypass for network and Unregister; Clear site data also removes the worker and its Cache Storage.
  • Private window: empty caches and no service worker, the view of a first-time visitor. Close other private windows first: they share one session and its cache.

Cache busting: how a release reaches the browser

Teams want files that are downloaded once and reused for a long time, and releases that every user receives at once. Cache busting gives both: when a file changes, its URL changes. Build tools put a fingerprint, a short code calculated from the file's content (a hash), into the file name, so app.3f9c1e.js becomes app.9b41d2.js; some teams add a version to the query string instead (app.js?v=2.15.0). The browser has never stored the new URL, so it must download it.

This works only if the HTML that names the files is fresh itself. Read the set-up below like this:

  • The HTML gets no-cache: every visit revalidates it and receives the new file names.
  • Fingerprinted files can be cached for a year, because any change gives them a new name.
  • A file without a fingerprint, like logo.svg, stays old for up to its max-age after a deploy.
  • A tab opened before the deploy still runs the old code. If it later requests a file that the deploy removed, such as the script for another screen, the request fails with 404, so teams keep the previous release's files online for a while.
Request                      Cache-Control                 After a deploy
GET /checkout                no-cache                      revalidated: new HTML
GET /assets/app.9b41d2.js    max-age=31536000, immutable   new name: downloaded
GET /assets/app.51c7aa.css   max-age=31536000, immutable   new name: downloaded
GET /images/logo.svg         max-age=86400                 old for up to 1 day
GET /api/cart                no-store                      never stored

Try it: Cache Header Analyzer Try it: HTTP Header Builder

Stale-content defects and how to recognise them

A stale-content failure shows an older version of a page, a file or data than the server now holds. The defect behind it is usually in the caching set-up, not in the feature under test: a missing fingerprint, a max-age that is too long, HTML or API responses that should not be cached, or a service worker that never updates. Mind the two meanings of "stale": the copy behind such a failure is usually still fresh by its headers, so the browser does not ask the server. In RFC 9111, "stale" only means that a copy's lifetime has run out, and a stale copy is checked with the server.

To show that a cache is the cause, compare. Repeat the steps in a private window, read the Size column, where (memory cache), (disk cache) or (ServiceWorker) replaces a byte count, and compare the file names and the build number with what was deployed. Typical symptoms:

  • Part of a release is missing for some users: new HTML with old styles, or a feature that a hard reload shows but a colleague does not see.
  • Old data: a price changed in the admin, but the shop shows the old one because the API response was cached by the browser or a CDN (content delivery network, a set of shared caches between users and the site).
  • Mixed versions: old JavaScript calls an API that changed, and saving fails with 400 or a console error.
  • A site stuck on an old version, or showing its offline page while online: a service worker serving its own copies.
  • Personal data after logout: pressing Back shows the account page again.

Worked example: testing a release as a returning user

Release 2.15.0 of a shop changes the checkout button and its styles. The tester keeps a browser profile that plays a returning customer. Before the deploy she opens the checkout in it and saves the list of loaded file names; after the deploy she compares this warm (filled) cache with a cold (empty) one:

  • Open the checkout in that profile with a normal navigation and Disable cache off; note what you see and the build number.
  • Compare the Network rows with the saved list: does each changed file come from the server under a new name, or from (disk cache) with old content?
  • In a tab left open since before the deploy, open a screen not yet visited and submit a form.
  • If the site has a service worker, look for a waiting version in Application > Service workers; close every tab of the site, reopen it and confirm that the new version is active.
  • Open a private window: a first-time visitor must get the new release too.
Release 2.15.0 on staging, returning-customer profile, normal navigation

Name            Status  Size           Note
checkout        200     5.2 kB         new HTML, footer shows build 2.15.0
app.9b41d2.js   200     212 kB         new fingerprint
styles.css      200     (disk cache)   no fingerprint, max-age=604800
logo.svg        200     (disk cache)   not changed in 2.15.0: fine

Result: "Pay now" is blue here (old styles) and green in a private window.
Bug:    styles.css has no fingerprint and is cached for 7 days, so returning
        customers see the 2.15.0 checkout with 2.14 styles until it expires.

Try it: HTTP Headers Parser Try it: Text Diff

Common mistakes

  • Testing a release only with a cold cache (a private window, a hard reload, Disable cache left on), so you never see what returning users see.
  • Answering a stale-content report with "clear your cache". Users do not clear caches; report the caching defect instead.
  • Forgetting the service worker: the page looks right after a hard reload, and the next normal load is served by the old worker again.
  • Assuming the page shows the new build. Check the build number or the file fingerprints before you test anything else.
  • Leaving the cache state out of the bug report: the Size column value, the file names and their headers, the build, and whether you hard-reloaded.

The QA perspective

Raise caching questions during refinement, whenever a story touches the front end: which files are fingerprinted, which Cache-Control the HTML, the files and the API responses get, how long old files stay online, and how the service worker updates and asks users to reload.

After each deploy, test with a returning-user profile first, add one check that bypasses every cache, and compare the two. In a stale-content report, record the build expected and the build seen, the affected URL with its Cache-Control and Size column value, the browser and profile, and whether a private window shows the new version. Developers can then find the misconfigured file instead of guessing.

Practice exercise

Predict what a returning customer sees after a release

Your task

Release 3.4.0 adds a VAT line to the invoice page, changes the brand colour and replaces the logo. It went live on Tuesday at 10:00. A customer opened the invoice once before, on Monday at 18:00, when the browser stored all its files, and returns on Tuesday at 11:00 with a normal navigation. The site has no service worker.

Using the headers below, decide for each request whether the browser uses its stored copy or the server, describe what the customer sees, and name the defects and how you would confirm them.

Material
Release 3.4.0, live Tuesday 10:00. No service worker.

Request                         Cache-Control                 Note
GET /invoice/1001               no-cache                      ETag changed in 3.4.0
GET /static/invoice.81f2a0.js   max-age=31536000, immutable   new name in 3.4.0
GET /static/styles.css          max-age=604800                changed in 3.4.0
GET /static/logo.png            max-age=86400                 changed in 3.4.0
GET /api/invoices/1001          no-store                      VAT line data
Steps
  1. Work out until when each stored copy stays fresh.
  2. Decide for each request: download, revalidation, stored copy or never stored.
  3. Describe the page the customer sees.
  4. Name the defects and a check that confirms them.
Show the model answer
Model answer

The new parts arrive: /invoice/1001 is revalidated because of no-cache, and the changed ETag brings the new HTML with the VAT line; invoice.81f2a0.js is a URL the browser has never stored, so it is downloaded; the no-store API response always comes from the server.

The old parts stay: styles.css was stored on Monday at 18:00 with max-age=604800 (7 days), so it is fresh until the next Monday at 18:00 and is used without asking the server. logo.png has max-age=86400 (1 day) and stays fresh until Tuesday at 18:00.

The customer sees the new invoice with the VAT line, but in the old colour with the old logo, and the VAT line may lack its new styles. Defect: styles.css and logo.png have no fingerprint, so returning users get the new page with the previous release's styles for up to 7 days and the old logo for up to 1 day. Choosing the fix, for example fingerprinting both files, is up to the developers.

Confirmation: in a profile that loaded the invoice before the release, open it normally and read the Size column, which shows (disk cache) for both files; then open the page in a private window, where the new colour and logo appear. The difference shows that the cache, not the release, is the cause.

Frequently asked questions

What is the difference between a hard reload and clearing the cache?

A hard reload ignores the stored copies for one load: the browser requests the page and its files again and skips the service worker, but keeps the cache. Clearing the cached files deletes the copies. Neither removes a service worker; Clear site data in the Application panel, or Unregister, does.

If clearing the cache fixes it, is it still a defect?

Yes. Customers do not clear their caches, so they keep seeing the failure. The defect is in the caching set-up, such as a missing fingerprint or a max-age that is too long. Its severity depends on what the old content breaks: an old logo is minor, an old price or a failing payment is not.

Does "Disable cache" in DevTools also turn off the service worker?

No. It turns off the HTTP cache while DevTools is open, but a service worker can still answer from its Cache Storage. In Chrome, use Bypass for network in Application > Service workers, or test in a private window.

References

  • RFC 9111: HTTP CachingIETF · 2022How Cache-Control, Expires, ETag and validators decide whether a response may be stored and reused.
  • HTTP caching — MDNMDN Web Docs (Mozilla)Private vs. shared caches, freshness, validation with ETag / Last-Modified and the common Cache-Control patterns.
  • Service Worker API — MDNMDN Web Docs (Mozilla)Service workers: scripts that sit between a web app and the network to intercept requests, manage caches and keep the app working offline.

Practise with Mutqan tools

Analyze Cache-Control, Expires, ETag, Last-Modified, Vary and Age headers and explain how browsers and CDNs will cache the response.

Validation

Parse raw HTTP request/response headers into a structured table with explanations.

Web

Compose HTTP headers with presets for caching, CORS, security and content negotiation.

Web

Compare two texts line by line or word by word and highlight differences.

Formatting

Go deeper

Your progress on this topic

Part of the QA Roadmap