> ## Documentation Index
> Fetch the complete documentation index at: https://docs.37audits.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CDN Coverage

> Checks whether the page and every asset it loads are delivered through a CDN, by hostname, response headers or CNAME chain.

<Info>
  **Performance Auditor** — checks that the audited page and every first-load asset it references (stylesheets, scripts, images, fonts and media) are delivered through a Content Delivery Network.
</Info>

<CardGroup cols={2}>
  <Card title="Speed" icon="gauge-high">
    A CDN serves the page and its assets from an edge close to each visitor instead of from one origin server.
  </Card>

  <Card title="Resilience" icon="shield-halved">
    Traffic spikes and attacks are absorbed at the edge, so the origin stays reachable.
  </Card>
</CardGroup>

A CDN keeps copies of your pages and assets on servers spread around the world and answers each visitor from the closest one. Pages load faster, the origin server handles a fraction of the traffic, and a sudden spike or a denial-of-service attack is absorbed at the edge. A page that is only partly on a CDN loses most of that benefit: the browser still waits for the slowest asset, and the origin is still exposed. This auditor checks the HTML document and every first-load asset it discovers.

## How it works

1. **Fetch** — downloads the audited URL once as 37AuditsBot. A blocked request ends the audit with `590`; an `IOException` or an HTTP status of 400 or higher ends it with `500`.
2. **Discover** — parses the HTML and collects every first-load asset: `link[rel=stylesheet]`, `script[src]`, `img[src]`, every candidate of `img[srcset]`, font preloads (`link[rel=preload][as=font]` and `link[type*=font]`), `video[src]` and `source[src]`. URLs are normalized the way browsers accept them (spaces, `|` and non-ASCII characters are percent-encoded). Inlined `data:` URIs never leave the HTML, so they are listed but not judged.
3. **Classify** — a resource is on a CDN when, in this order, its hostname ends with a known CDN suffix (`.cloudfront.net`, `.cloudflare.net`, `.fastly.net`, `.akamaihd.net`, `.azureedge.net`, `.b-cdn.net` and others), its response carries a CDN fingerprint header (`cf-cache-status`, `x-amz-cf-id`, `server: cloudflare`, `server: AkamaiGHost`, `via: … cloudfront`, `x-served-by: cache-…` and others), or its CNAME chain ends on a known CDN hostname. The HTML is classified from the response already fetched; each asset on an unknown hostname is probed with a `HEAD` request (`GET` when `HEAD` is refused). An asset whose probe throws counts as on a CDN when its CNAME points to one, otherwise it is reported with `501` and left out of the decision.
4. **Rules** — when no resource at all is on a CDN, every backend resource fails (`400`). When at least one resource is on a CDN, every backend resource warns (`300`).
5. **Aggregate** — FAIL wins over WARNING; when every probed resource is on a CDN the audit is `SUCCESS` (`200`), with the counts, the detection method of each resource and the inlined data URIs in the check data. A page whose HTML is on a CDN and whose assets all failed to probe is `SUCCESS` with the `501` checks attached.

## What it audits

Every row maps to one `checkRule*` method and one `CheckCode`, in the order the auditor runs them. CheckCode suffixes use status ranges: `200` success, `300–399` warning, `400–499` fail, `500–599` error.

| CheckCode | Status | When it fires | Recommendation | Why it matters |
| - | - | - | - | - |
| <a id="37A-CDNCoverageAuditor-400" />`37A-CDNCoverageAuditor-400` | `FAIL` | One check per resource served directly from the backend when no resource of the page at all is on a CDN. | Put `{host}` behind a CDN such as Cloudflare, CloudFront, Fastly or Akamai so `{resource}` is delivered from an edge close to the visitor. | Without a CDN every visitor downloads the page and its assets from the origin server, so pages load slower for people far from it, traffic spikes hit the origin directly and the site is easier to take down. |
| <a id="37A-CDNCoverageAuditor-300" />`37A-CDNCoverageAuditor-300` | `WARNING` | One check per resource served directly from the backend when at least one other resource of the page is on a CDN. | Serve `{resource}` through the CDN that already delivers the rest of `{url}`, for example by routing `{host}` through it. | Assets that bypass the CDN load slower than the rest of the page and keep the origin exposed to traffic spikes, so the CDN cannot deliver its full performance and resilience benefit. |
| <a id="37A-CDNCoverageAuditor-501" />`37A-CDNCoverageAuditor-501` | `ERROR` | One check per asset whose probe threw (timeout, DNS, TLS, refused connection) while its CNAME chain does not point to a CDN. The asset is left out of the `300` / `400` decision. | Make sure `{resource}` answers HTTP requests from 37AuditsBot, then run the audit again. Last error: `{error}`. | — |
| <a id="37A-CDNCoverageAuditor-200" />`37A-CDNCoverageAuditor-200` | `SUCCESS` | Every probed resource (the HTML and each discovered asset) is served through a CDN. Message gives the resource count, the number of assets that could not be probed and the number of inlined data URIs; data lists the detection method (`hostname`, `headers` or `cname`) of each resource. | — | — |
| <a id="37A-CDNCoverageAuditor-500" />`37A-CDNCoverageAuditor-500` | `ERROR` | The audited page could not be fetched (timeout, DNS, TLS, or an HTTP status of 400 or higher other than a block). | Make sure `{url}` answers with HTTP 2xx, then run the audit again. Last error: `{error}`. | — |
| <a id="37A-CDNCoverageAuditor-590" />`37A-CDNCoverageAuditor-590` | `ERROR` | The audited page answered HTTP 403, HTTP 429 or a Cloudflare challenge, so 37AuditsBot was blocked before it could read the page. | Allow 37AuditsBot in your firewall or bot protection: allowlist its user agent, or verify its Web Bot Auth signature. See [https://www.37audits.com/bot](https://www.37audits.com/bot). | While 37AuditsBot is blocked this audit cannot look at the site, so problems on it go unreported. |

Every per-resource check carries `resource`, `kind` (`html`, `stylesheet`, `script`, `image`, `font` or `media`), `cdnDetection` (`hostname`, `headers`, `cname` or `none`) and `httpStatus` (`null` when the resource was not requested) in its data. The `200` check adds `resourceCount`, `cdnCount`, `inlinedCount`, `inlinedResources` and the per-resource list.

## Output documentation

| Status | Description | Test logic |
| - | - | - |
| **SUCCESS** | The page and every asset it loads are on a CDN | No probed resource is served from the backend (`200`). Assets whose probe failed (`501`) do not count against the page. |
| **WARNING** | Some resources bypass the CDN | At least one resource is on a CDN and at least one is served from the backend; one `300` check per backend resource. |
| **FAIL** | Nothing is on a CDN | No resource is on a CDN; one `400` check per backend resource (the HTML included). |
| **ERROR** | The auditor could not read the page | Fetch failure or HTTP 400+ on the audited page (`500`); site blocked 37AuditsBot (`590`). A failed asset probe (`501`) is attached to the result but does not make it `ERROR`. |

## Risks and considerations

### Why CDN coverage matters

* Visitors far from the origin server wait for every byte to cross the distance; a CDN edge answers from nearby and cuts load time, which drives conversions and search rankings.
* The origin only sees cache misses, so a traffic spike, a viral post or a volumetric attack is absorbed at the edge instead of taking the site down.
* One asset that bypasses the CDN is enough to keep the page waiting and the origin exposed, which is why the audit looks at every first-load asset and not only at the HTML.

### How CDNs are recognised

* A known hostname suffix is trusted without a request, so assets on `*.cloudfront.net`, `*.fastly.net` or `*.b-cdn.net` are never fetched.
* Header fingerprints cover the major providers (Cloudflare, CloudFront, Akamai, Fastly, Imperva, Google). A CDN configured to strip its headers on a custom domain is only found through its CNAME chain.
* The CNAME chain is followed through DNS for up to ten hops. A CDN reached through an `A` record on the customer's own domain (for example an anycast setup with no CNAME) is not recognised, and that resource is reported as served from the backend.

### Behavior changes in this version

* A page that cannot be fetched (timeout, DNS, TLS, HTTP 400 or higher) is now `ERROR` with `500`; it used to be reported as `FAIL`.
* Mixed coverage now emits one `WARNING` check with `300` per backend resource; the result stays `WARNING` as before.
* An asset whose probe fails is now a `501` `ERROR` check that is left out of the decision; it used to push the result to `WARNING` on its own.
* The page is fetched once and `ensureNotBlocked` runs before any asset is probed; the old auditor probed the page before the block check and then fetched it a second time.
* Assets on a CDN no longer get one `SUCCESS` check each; the `200` check lists them in its data instead.
* `srcset` candidates are now read the way the HTML Standard does (a URL runs to the first whitespace), so an inlined `data:` URI with commas stays whole instead of producing a bogus backend resource from its second half.

### What this auditor does not check

* Only the first-load assets written in the HTML are discovered; resources requested by scripts, CSS `url()` references and lazy-loaded images are not followed.
* Whether the CDN actually caches the resource (hit ratio, `Cache-Control`) is left to the Cache auditor.
* Third-party assets (analytics, tag managers, embeds) are probed like any other asset; moving them is often not in the site owner's hands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.