# Static assets served from CDN with geographic edge caching

- **Pattern:** `ab-002018` (`performance-core.script-style-efficiency.cdn-caching`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002018
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002018)

## Why it matters

An origin server in us-east-1 adds 150–300ms of network latency for users in APAC or Europe before a single byte of content arrives (ISO-25010 time-behaviour). A CDN with geographic edge caching eliminates that latency by serving cached responses from the PoP nearest the user. Separately, static assets served without `Cache-Control: immutable, max-age=31536000` are re-validated on every visit, incurring a conditional request round-trip even when the asset has not changed. Both issues compound on high-traffic pages.

## Severity rationale

Info because CDN and cache-control gaps are genuinely harmful at scale but commonly resolved automatically by modern hosting platforms, making the finding most relevant to custom or self-hosted deployments.

## Remediation

Configure aggressive cache headers for versioned static assets in `next.config.ts`:

```ts
// next.config.ts
const config = {
  async headers() {
    return [
      {
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable'
          }
        ]
      }
    ]
  }
}
export default config
```

Verify CDN is active by checking response headers for a CDN-specific header:
```bash
curl -sI https://yoursite.com | grep -i 'x-vercel-cache\|cf-cache-status\|x-cache'
# HIT = served from CDN edge; MISS = reached origin
```

For self-hosted deployments without a built-in CDN, place Cloudflare (free tier sufficient) or AWS CloudFront in front of your origin. Ensure the CDN's cache rules allow caching of static assets.

## Detection

- **ID:** `cdn-caching`
- **Severity:** `info`
- **What to look for:** Count all relevant instances and enumerate each. Check the project's hosting configuration. Look for CDN usage (Vercel Edge Network, Cloudflare, AWS CloudFront, etc.). Verify assets are cached with `cache-control` headers. Check DNS or deployment config for CDN integration.
- **Pass criteria:** Static assets (JS, CSS, images) are served from a CDN with geographic edge caching. At least 1 implementation must be verified. Cache-Control headers are set: `immutable` with `max-age=31536000` for versioned assets, or `no-cache` for dynamic content.
- **Fail criteria:** Assets served from origin server only (no CDN), or CDN is not configured. Every request travels to origin regardless of geographic location. Cache headers missing or set to short TTL.
- **Skip (N/A) when:** The project is deployed to a single region with no geographic distribution needs.
- **Detail on fail:** Describe the CDN gap. Example: `"All requests go to origin in us-east-1; users in APAC experience 300ms+ latency. No CDN in front"` or `"CDN configured but cache headers not set; assets not cached at edges"`.
- **Remediation:** Modern platforms include CDN by default:

  **Vercel:** Uses Vercel Edge Network globally by default.
  **Netlify:** Uses Netlify Edge globally by default.
  **Cloudflare:** Global CDN with 200+ edge locations.

  Set cache headers:
  ```ts
  // next.config.ts
  const config = {
    headers: async () => {
      return [
        {
          source: '/static/:path*',
          headers: [
            {
              key: 'cache-control',
              value: 'public, max-age=31536000, immutable'
            }
          ]
        }
      ]
    }
  }
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance, operational-readiness

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