# API responses specify appropriate caching behavior

- **Pattern:** `ab-002072` (`performance-load.caching.api-cache-strategy`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002072
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002072)

## Why it matters

An API endpoint that returns product listings or blog posts without a `Cache-Control` header causes every client request to hit the origin server — even when the data hasn't changed in hours. At scale, this is avoidable server load that accumulates into real infrastructure cost and latency. Conversely, a user-specific endpoint (account data, cart, session) served with `Cache-Control: public` allows CDN or browser caching to serve one user's data to another — a privacy violation. ISO 25010 performance-efficiency.resource-utilization applies: public data should be cached to eliminate redundant computation; private data must be protected from shared caches.

## Severity rationale

Low because missing API cache headers waste server resources on repeated identical requests, but the impact is gradual and does not block functionality until traffic scales.

## Remediation

Add explicit `Cache-Control` headers to all API GET routes. Public data (product lists, blog posts, public stats) should be cached; user-specific data (account info, cart, orders) must be marked private or uncacheable.

```ts
// Next.js App Router — src/app/api/products/route.ts
export async function GET() {
  const products = await db.products.findAll()
  return Response.json(products, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400'
      // CDN caches for 1 hour, serves stale for 24h while revalidating
    }
  })
}

// User-specific data — must be private
export async function GET(request: Request) {
  const user = await getAuthenticatedUser(request)
  return Response.json(user, {
    headers: {
      'Cache-Control': 'private, no-cache'
      // Never cache in shared (CDN) cache
    }
  })
}
```

## Detection

- **ID:** `api-cache-strategy`
- **Severity:** `low`
- **What to look for:** Count all API route files in the project. For each GET endpoint, check whether a `Cache-Control` header is set in the response. Classify each as: (a) public data with appropriate cache headers (`public, max-age=...`), (b) private/user-specific data with appropriate headers (`private, no-cache` or `no-store`), or (c) no cache headers set. Enumerate: "X API routes found, Y set explicit Cache-Control headers."
- **Pass criteria:** At least 50% of API GET endpoints set explicit `Cache-Control` headers. Public data endpoints use `Cache-Control: public, max-age=...` with `max-age` of at least 60 seconds. User-specific endpoints use `Cache-Control: private, no-cache` or `no-store`. No more than 2 API routes should rely on browser default caching. Report: "X of Y API GET routes set explicit Cache-Control headers."
- **Do NOT pass when:** A public-data endpoint (e.g., product listings, blog posts) returns user-specific data headers (`private`) or no cache headers at all, causing every request to hit the server.
- **Fail criteria:** Fewer than 50% of API GET endpoints set any `Cache-Control` header, or a public data endpoint is marked as `private`/`no-store`.
- **Skip (N/A) when:** No API routes detected (0 API route files found — the project has no backend endpoints).
- **Detail on fail:** `"1 of 6 API GET routes sets Cache-Control — /api/products, /api/posts, and /api/categories all lack cache headers"` or `"User profile endpoint at /api/user uses Cache-Control: public — violates privacy for 500+ users"`
- **Remediation:** Add cache headers to API routes:

  ```ts
  // Next.js API route — public data
  export async function GET() {
    const data = await db.products.findAll()
    return Response.json(data, {
      headers: {
        'Cache-Control': 'public, max-age=3600' // cache for 1 hour
      }
    })
  }

  // User-specific data
  export async function GET() {
    const user = await getAuthenticatedUser()
    return Response.json(user, {
      headers: {
        'Cache-Control': 'private, no-cache'
      }
    })
  }
  ```

## External references

- iso-25010 performance-efficiency.resource-utilization

Taxons: performance

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