Study interactive :: Progress tools open in the Study Hub reader.

11. CDNs

A user in Sydney loads a website hosted on a server in Virginia. The packet has to fly across the Pacific and back. That's at least 250 ms of round-trip time before the server has even read the request. For a page that needs 50 assets, you can do the math: the page is going to feel slow.

The fix is obvious: put a copy of the content close to the user. That's what a CDN (Content Delivery Network) does.

What a CDN actually is

A CDN is a network of servers spread around the world, all serving the same content. Cloudflare, Akamai, Fastly, AWS CloudFront, and others operate them. They have hundreds of "edge locations" or "points of presence" (PoPs).

Your DNS routes users to the nearest one (GeoDNS, remember Chapter 5):

User in SydneySydney PoPUser in MumbaiMumbai PoPOrigin server

The first request from a region misses and reaches your origin. The PoP caches the response and serves the next requests directly.

What gets cached

CDNs were originally for static content: images, CSS, JS, video. Those are easy: same URL, same bytes, cache for hours or days.

Modern CDNs also cache:

What doesn't make sense to cache:

How a CDN decides what to do

The CDN sits between client and origin and follows HTTP cache headers. The two big ones:

Cache-Control: max-age=3600, public

Cache for 3600 seconds (1 hour). public means CDN can cache it for everyone. private means only the user's browser can.

Cache-Control: no-store

Never cache. Always go to origin.

ETag-based revalidation is the other lever. The CDN can ask the origin "is this still the latest?" instead of refetching the whole asset.

GET /image.jpg HTTP/1.1
If-None-Match: "v1abc"

HTTP/1.1 304 Not Modified

The 304 response has no body, just "yes, your cached copy is fine". The CDN serves the cached body.

Push vs pull CDNs

Two models:

Cloudflare and Fastly are pull. CloudFront supports both. For 99% of use cases, pull is what you want. It's hands-off.

Origin shielding

When a CDN has hundreds of edges, a cold cache means hundreds of edges all hit your origin on the first request. Origin shielding adds one "shield" PoP in between that absorbs those misses. Other PoPs pull from the shield, not directly from origin.

missmissUserEdge PoPShield PoPOrigin

You set this up in the CDN config. Big traffic spikes will thank you for it.

Cache invalidation

Files change. You want users to see the new version, not the cached old one.

Three ways:

1. Versioned URLs (the best way): change the URL when the content changes. Bundlers do this automatically: app.a3f8b1.js becomes app.e7c92d.js after a build. Users get the new file, the old one just sits in the cache until it ages out.

2. Cache busting query strings: app.js?v=2. Same idea, uglier.

3. Purge / invalidate: tell the CDN to throw out a specific URL.

# Cloudflare example
curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone>/purge_cache" \
  -H "Authorization: Bearer <token>" \
  -d '{"files":["https://example.com/style.css"]}'

Purges are slow (sometimes minutes) and have rate limits. Don't rely on them for frequent updates. Use versioned URLs as the default.

CDNs as more than caches

Modern CDNs are full edge platforms:

For many projects, the CDN is now half the architecture.

Code: a tiny Cloudflare Worker

This is the kind of code that runs at every edge PoP, microseconds from the user:

export default {
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return new Response("ok", { headers: { "Cache-Control": "no-store" } });
    }

    // pass everything else to origin, but add a header
    const res = await fetch(request);
    const out = new Response(res.body, res);
    out.headers.set("X-Edge", "hello-from-the-edge");
    return out;
  },
};

You deploy it to one URL, and it runs in 300+ cities at the same time. That's the value proposition.

Things to remember

Going deeper