# Static assets have long-lived cache headers configured

- **Pattern:** `ab-001802` (`marketing-page-speed.infrastructure.static-asset-caching`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001802
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001802)

## Why it matters

Browser cache headers control whether a returning visitor re-downloads assets on every page load or serves them from the local disk cache in milliseconds. Without `Cache-Control: public, max-age=31536000, immutable` on hashed static assets, every visit — including from users who visited yesterday — initiates fresh network requests for all JavaScript bundles, CSS, and images. ISO-25010 resource-utilisation covers this: downloading the same unchanged 200KB bundle on every repeat visit is pure waste. Modern frameworks (Next.js, Vite, webpack) include content hashes in filenames, making year-long browser caches safe — changing any file produces a new filename, so stale cache never serves outdated content.

## Severity rationale

Medium because absent long-lived cache headers cause repeat visitors to re-download all assets unnecessarily, increasing page load times and bandwidth costs, but the impact is limited to returning users rather than first-visit acquisition.

## Remediation

Configure long-lived immutable cache headers for hashed static assets and short-lived revalidation headers for HTML. On Vercel with Next.js, static chunks already receive `max-age=31536000, immutable` — verify this is not overridden.

```json
// vercel.json — explicit rules for custom asset types
{
  "headers": [
    {
      "source": "/(.*)\\.(js|css|woff2|webp|avif)$",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    },
    {
      "source": "/(.*)\\.html$",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
      ]
    }
  ]
}
```

For custom Express servers, add `serve-static` with `maxAge: '1y'` for the `public/` directory, or set headers explicitly in middleware for the `/_next/static/` path.

## Detection

- **ID:** `static-asset-caching`
- **Severity:** `medium`
- **What to look for:** Static assets (JS, CSS, images, fonts) should be cached by the browser for as long as possible. When filenames include content hashes (which modern frameworks do automatically), changing the file changes the filename — so `max-age=31536000` (1 year) is safe. Check: (1) `vercel.json` headers config for static asset cache rules, (2) `netlify.toml` headers config, (3) `next.config.*` headers function, (4) Cloudflare Page Rules, (5) nginx/Apache config. Note: Vercel + Next.js automatically applies `max-age=31536000, immutable` to hashed static chunks — if that hosting combination is detected, this check passes by platform default. Count all instances found and enumerate each.
- **Pass criteria:** Static assets (JS, CSS, images, fonts) have `Cache-Control: public, max-age=31536000, immutable` headers. HTML has `Cache-Control: public, max-age=0, must-revalidate` (short cache with revalidation). Vercel + Next.js hosting satisfies this automatically. At least 1 implementation must be confirmed.
- **Fail criteria:** Static assets have no cache headers, short cache (max-age < 86400), or `no-cache`. Every page load re-fetches all assets.
- **Skip (N/A) when:** Project is not yet deployed and no hosting config file is present.
- **Detail on fail:** `"Custom Express server serves static assets with no Cache-Control headers. Every page load downloads all JS and CSS fresh — no browser caching benefit."`
- **Remediation:** Configure long-lived caches:

  ```json
  // vercel.json — explicit cache rules (not needed for Next.js static chunks)
  {
    "headers": [
      {
        "source": "/(.*)\\.(js|css|woff2|png|jpg|webp|avif)$",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
        ]
      },
      {
        "source": "/(.*)\\.html$",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
        ]
      }
    ]
  }
  ```

## External references

- iso-25010 performance-efficiency.resource-utilization

Taxons: performance

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