# Cache-Control headers are present

- **Pattern:** `ab-002538` (`site-health-check.performance-signals.cache-headers`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002538
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002538)

## Why it matters

Without `Cache-Control` headers, browsers fall back to heuristic caching — typically caching responses for 10% of their apparent age, or not at all. Every repeat visitor re-downloads content that hasn't changed, wasting bandwidth and adding latency on every navigation. Properly configured caching reduces server load, speeds up repeat visits, and improves CDN hit rates. ISO 25010 performance-efficiency identifies caching as a core mechanism for resource utilization. The absence of cache headers forces both browsers and CDNs into conservative or undefined behavior, leaving significant performance on the table.

## Severity rationale

Medium because missing cache headers cause preventable latency on repeat visits and increase server load, but the site remains functional — users are slowed, not blocked.

## Remediation

Set `Cache-Control` explicitly on every response type. Match the directive to the content's mutability:

```nginx
# Static assets (images, fonts, versioned JS/CSS)
add_header Cache-Control "public, max-age=31536000, immutable" always;

# HTML pages (content changes; revalidate)
add_header Cache-Control "public, max-age=3600, must-revalidate" always;

# Authenticated or personalized pages
add_header Cache-Control "private, no-store" always;
```

In Next.js API routes: `res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=86400')`. For `app/` router, use `export const revalidate = 3600` in route segments. Confirm with `curl -sI https://yoursite.com | grep -i cache-control`.

## Detection

- **ID:** `cache-headers`
- **Severity:** `medium`
- **What to look for:** Extract the `Cache-Control` header value from the final document response. Count the number of cache directives present (e.g., `max-age`, `s-maxage`, `no-cache`, `no-store`, `public`, `private`, `must-revalidate`). Parse any `max-age` or `s-maxage` value to determine the cache duration in seconds.
- **Pass criteria:** A `Cache-Control` header is present on the main document response with at least 1 recognized cache directive. The header value must not be empty. Report the directive(s) found and the `max-age` value if present (e.g., "Cache-Control: public, max-age=3600 — 1 hour cache").
- **Fail criteria:** No `Cache-Control` header on the main document response, or the header value is empty.
- **Skip (N/A) when:** The response includes `Set-Cookie` headers, as caching responses with cookies can leak user data and servers may intentionally omit caching in this case.
- **Detail on fail:** `"No Cache-Control header on main document — browsers cannot cache optimally"`
- **Remediation:** Cache-Control headers let browsers and CDNs cache responses, reducing load times on repeat visits. Add appropriate caching for your content:

  ```
  # Static marketing pages — cache for 1 hour, revalidate
  Cache-Control: public, max-age=3600, must-revalidate

  # Dynamic app pages — no cache
  Cache-Control: no-store
  ```

  In Next.js, static pages get caching automatically. For custom headers, use `next.config.js` `headers()` or set them in API routes: `res.setHeader('Cache-Control', 'public, max-age=3600')`. Verify with: `curl -sI {URL} | grep -i cache-control`

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-002538
