Browser / API Request Debugging workflow

Why Cache-Control Makes API Responses Look Stale

Debug stale API responses by checking Cache-Control, ETag, 304 responses, browser cache, CDN rules, service workers and request cache options.

Quick Answer

An API response looks stale when a browser, CDN, proxy or service worker serves cached data longer than expected. Inspect Cache-Control, ETag, Age, 304 responses and service worker behavior before assuming the backend returned old data.

Example Scenario

The database was updated, the backend logs show fresh data, but the browser still displays the old response. Hard refresh sometimes fixes it. Another user sees the new value immediately. The stale layer may be browser cache, CDN cache, an application data cache or a service worker.

Step-by-Step Explanation

  1. Check response headers for Cache-Control, ETag, Age and Vary.
  2. Look for 304 Not Modified responses in the Network panel.
  3. Compare browser, curl and authenticated requests.
  4. Check whether a CDN or reverse proxy is caching the endpoint.
  5. Inspect service worker and application-level query cache behavior.
  6. Set cache policy based on data sensitivity and freshness needs.

Caching Has Multiple Layers

A stale response can come from the browser, a CDN, a reverse proxy, a service worker, a backend memory cache or a frontend query library. The backend database can be correct while the user still sees old data.

Start by identifying which layer served the response. Headers such as Age, Via, ETag, Cache-Control and platform-specific cache status can point to a CDN or proxy. DevTools can show whether the browser used disk cache, memory cache or a 304 validation path.

Without layer identification, teams often clear random caches until the symptom disappears, then fail to prevent it from returning.

Cache-Control Describes Freshness Rules

Cache-Control can say that a response is public, private, no-store, no-cache, immutable or fresh for a max-age duration. These directives have different meanings. no-cache does not mean never store; it means revalidate before reuse. no-store is stricter.

For user-specific API data, private or no-store may be appropriate depending on sensitivity. For public reference data, caching can be helpful. The policy should match the data contract rather than being copied from static assets.

A common bug is applying long-lived static asset caching to JSON endpoints. That makes API data behave like versioned CSS or JavaScript files, which is rarely intended.

ETag and 304 Can Be Correct or Misleading

ETag lets a client ask whether a resource changed. If the server returns 304 Not Modified, the browser reuses its cached body. This is efficient when the validator is correct. It is confusing when the ETag does not change after the underlying data changes.

If you see 304, check the original cached response body and the validator logic. A backend may compute ETag from the wrong fields, or a proxy may validate against a stale upstream representation.

During debugging, temporarily disable cache in DevTools or send cache-control request directives to compare a forced fresh response with the normal path.

Vary and Authorization Matter

The Vary header tells caches which request headers affect the response. If an API response varies by Authorization, Accept-Language or feature flag header but the cache does not know that, one user or locale can see another representation.

Authenticated responses need special care. Shared caches should not store user-specific data unless the configuration is explicitly safe. A missing private directive can become both a freshness bug and a privacy bug.

When comparing stale and fresh responses, include the request headers that affect representation. The URL alone may not explain the cache key.

Service Workers and Query Caches Can Hide the Network

A service worker can serve a cached response even when the Network panel looks different from a normal fetch. Frontend query libraries can also reuse cached data without making a request at all. In those cases, backend logs may show nothing because no network call happened.

Check the application tab for service workers and storage. Also inspect query cache settings such as staleTime, cacheTime or revalidation behavior. A stale UI may be caused by application state rather than HTTP caching.

Distinguish “network response is stale” from “UI state is stale.” They look similar to users but require different fixes.

Personalized Data Should Not Share a Public Cache Key

If an endpoint returns user-specific data, the cache key must account for the user context or the response should not be stored in a shared cache. Missing Vary headers or overly broad CDN rules can turn a freshness bug into a data exposure risk.

Check whether Authorization, cookies, locale and feature-flag headers affect the body. If they do, the caching layer needs to know that or the endpoint should use private or no-store policy.

Do not debug only with an administrator account. Test two different users with different data so accidental cross-user caching is visible.

What to Check Next

Capture two requests: one normal and one forced fresh. Compare headers, status, body and cache indicators. If forced fresh is correct, the stale layer is likely cache policy. If both are stale, the backend or data pipeline may be returning old data.

Use Text Diff Checker to compare headers and JSON Compare to compare bodies. Header differences often explain why two identical-looking URLs behave differently.

After fixing cache policy, add a regression check for the affected endpoint. Freshness bugs are easy to reintroduce when CDN rules or framework defaults change.

Check both anonymous and authenticated requests. A public marketing endpoint and a private account endpoint may share path patterns but need completely different cache policy.

When a CDN is involved, document the purge path. If the next bad response is cached globally, the team should know whether to purge by URL, surrogate key, tag or deployment.

Add a response header that identifies the application version or data version when appropriate. It gives support teams a quick way to tell whether the user is seeing an old response, an old deployment or merely old application state. Keep the header non-sensitive and stable enough to compare across screenshots and logs.

For high-risk endpoints, add cache policy checks to integration tests. A route that returns private account data should fail tests if it accidentally receives a public max-age directive. Test logout and user switching as well, because identity changes reveal stale private responses.

Code Examples

Fetch while bypassing browser cache for debugging
const response = await fetch('/api/profile', { cache: 'no-store' });
console.log(response.status, response.headers.get('cache-control'));
console.log(await response.json());
Safer header for sensitive user data
Cache-Control: no-store
Vary: Authorization
Log cache-relevant response headers
for (const name of ['cache-control', 'etag', 'age', 'vary']) {
  console.log(name, response.headers.get(name));
}

Common Mistakes

  • Assuming stale UI always means stale backend data.
  • Confusing no-cache with no-store.
  • Applying static asset cache rules to JSON endpoints.
  • Ignoring 304 responses because the final status looks successful.
  • Forgetting service worker and frontend query cache layers.

FAQ

Does no-cache mean never cache?

No. It means the cache should revalidate before reuse. no-store is the directive that prevents storing.

What does 304 mean?

304 means the cached body can be reused because the server says the resource has not changed.

Can a service worker serve stale API data?

Yes. Service workers can intercept requests and return cached responses.

Why does curl show fresh data while the browser is stale?

Curl may bypass browser cache, service workers and frontend query caches.