CDN & Edge Compute: Serving Content and Running Code Close to Users
How a CDN caches and distributes your assets, how cache-control and TTL and invalidation actually work, and when edge functions beat your origin, and when they don't.
Your origin server lives in one place, say, a data center in Virginia. A user in Sydney requests your homepage. That request crosses an ocean, hits your server, and the response crosses back. Even at the speed of light through fiber, that round trip is ~250ms before your app does any work. Add a few more round trips for images, scripts, and fonts, and the page feels sluggish, not because your code is slow, but because the data is simply *far away*.
A CDN (Content Delivery Network) fixes the distance problem by keeping copies of your content in hundreds of locations worldwide, so the Sydney user is served from a server in Sydney. Edge compute takes the next step: it lets you run small bits of code at those same locations, so even dynamic logic happens close to the user instead of 3,000 miles away.
Who this is for
Developers and junior cloud engineers who ship a web app or API and want it to feel fast everywhere, not just near the origin. If you've heard "just put a CDN in front of it" and weren't sure what that actually does (or why your dynamic pages broke when you did), this is for you.
The mental model
A CDN is a globe-spanning cache that serves your content from wherever the user is; edge compute is the ability to run code in that same cache, so decisions happen near the user instead of at the origin.
The central warehouse holding all stockYour origin server, the source of truth
Local corner shops in every neighborhoodEdge PoPs (Points of Presence) worldwide
Popular items kept on the shop shelfCached assets (cache hit, served locally, fast)
Shop is out, so it phones the warehouseCache miss, PoP fetches from origin, then caches
"Sell-by" date on each itemTTL, how long the cached copy stays fresh
A clerk who can answer simple questions on the spotEdge function, small compute running at the PoP
A CDN is a chain of local corner shops, restocked from one central warehouse.
The whole game is moving work and data closer to the user. The further down the list you can answer a request, at the shop instead of the warehouse, the faster and cheaper it is.
The picture: a request through the edge
A user hits the nearest PoP. A cache hit returns instantly; a miss fetches from origin. An edge function runs at the PoP itself.
1
User hits the nearest PoP
DNS (or anycast routing) sends the request to the geographically closest Point of Presence, not your origin. This is the first big latency win.
2
PoP checks its cache
It builds a cache key (usually method + host + path, sometimes query string or headers) and looks for a fresh copy.
3
Cache hit → served locally
If a fresh copy exists, the PoP returns it immediately. The origin never even hears about this request.
4
Cache miss → fetch from origin
No fresh copy? The PoP requests it from the origin, streams it back to the user, and stores a copy for the next visitor, governed by the response's cache headers.
5
Edge function runs at the PoP (optional)
For requests that need logic, auth checks, redirects, A/B routing, personalization, a small function executes at the PoP, often without touching the origin at all.
Where each kind of work belongs
The edge is fantastic for some things and useless for others. The deciding question is almost always: can this be cached or computed without the origin's data? Here's the rough map.
Dimension
At the edge (PoP)
At the origin
Best for
Static, public, read-heavy content
Dynamic, private, write-heavy logic
Static assets (JS, CSS, images, video)
Cache aggressively, long TTL, served from PoP
Origin only on first miss
Dynamic / personalized HTML
Risky, cache only with care (vary keys, short TTL)
Rule of thumb: **cache what's the same for everyone, compute at the origin what's unique to one user.** Edge functions sit in between, use them for fast, stateless decisions, not for your whole backend.
Caching in practice: cache-control and a tiny edge function
The CDN doesn't guess how long to cache, *your response headers tell it*. The single most important header is Cache-Control. Get it right and the edge does the heavy lifting; get it wrong and you either serve stale data or never cache at all.
response headers
http
# A static asset with a content hash in its name, cache forever.
# "immutable" tells the browser not to even revalidate.
Cache-Control: public, max-age=31536000, immutable
# An HTML page that can be served stale briefly while refreshing
# in the background (great for read-heavy pages).
Cache-Control: public, max-age=60, stale-while-revalidate=300
# Private, per-user content, never cache at a shared PoP.
Cache-Control: private, no-store
`max-age=N`, how long (seconds) the copy is fresh. This is your TTL.
`public` vs `private`, public may be cached by shared CDN PoPs; private means only the user's own browser may cache it (never the edge).
`no-store`, do not cache anywhere. Use for anything sensitive or truly per-request.
`stale-while-revalidate=N`, serve the stale copy instantly for N extra seconds while fetching a fresh one in the background. Users never wait on the miss.
`immutable`, the content at this URL will never change (use with hashed filenames like app.9f3a2.js), so skip revalidation entirely.
Now a tiny edge function. This one runs at every PoP, redirects EU visitors to a regional path, and adds a security header, all without a round trip to the origin. The exact API varies by provider (Cloudflare Workers, Vercel Edge, Lambda@Edge), but the shape is the same: read the request, decide, respond or pass through.
edge-function.js
javascript
// Runs at the PoP, milliseconds from the user.exportdefault {
asyncfetch(request) {
const url = newURL(request.url);
const country = request.headers.get("cf-ipcountry") || "US";
// Geo-redirect EU users, decided at the edge, no origin hop.const EU = newSet(["DE", "FR", "NL", "ES", "IT"]);
if (EU.has(country) && !url.pathname.startsWith("/eu")) {
url.pathname = "/eu" + url.pathname;
return Response.redirect(url.toString(), 302);
}
// Otherwise fetch (cached) origin content and harden the response.const response = awaitfetch(request);
const headers = newHeaders(response.headers);
headers.set("X-Frame-Options", "DENY");
returnnewResponse(response.body, { ...response, headers });
},
};
Notice what this function does *not* do: it doesn't query a database, run an ORM, or hold state. Edge runtimes are deliberately lightweight (small CPU/memory budgets, short execution limits). That constraint is the point, it's what keeps them fast and cheap to run in hundreds of places at once.
Cache invalidation: the hard part
There's an old joke that the two hardest problems in computer science are naming things, cache invalidation, and off-by-one errors. Caching is easy; *knowing when the cached copy is wrong* is the hard part. You have three main tools.
TTL expiry (let it lapse). The cleanest option: set a sensible max-age and let copies expire on their own. No action needed, but stale data lives for up to one TTL. Great for content that tolerates being a little behind.
Cache busting (change the URL). Put a content hash in the filename (app.9f3a2.js). When the file changes, the URL changes, so the old cache entry is simply never requested again. This is why you can cache hashed assets immutable for a year, the standard trick for static builds.
Active purge (tell the CDN to forget). Call the CDN's purge API to evict a specific path (or a tag, or everything) right now. Use it when you must force-refresh published content immediately, but it's slower to propagate and easy to over-use.
purge.sh
bash
# Targeted purge of one path (fast, surgical), preferred.
curl -X POST "https://api.cdn.example/zones/$ZONE/purge" \
-H "Authorization: Bearer $TOKEN" \
-d '{ "files": ["https://site.com/pricing"] }'# Purge everything (the nuclear option), slow to repopulate,# briefly hammers your origin on the cold cache. Avoid as routine.
curl -X POST "https://api.cdn.example/zones/$ZONE/purge" \
-H "Authorization: Bearer $TOKEN" \
-d '{ "purge_everything": true }'
Watch out
Prefer **cache busting over purging** for assets, and **TTL + stale-while-revalidate** over purging for pages. Reach for active purge only for the genuine "I published something wrong, fix it now" case. A purge-everything on every deploy turns your CDN into a slow proxy.
Common mistakes that cost hours
Caching dynamic or private data. Serving a public-cached page that includes one user's name or cart means the *next* visitor gets *their* data. If a response varies per user, mark it private / no-store, or vary the cache key on the right header.
No TTL strategy. Leaving headers to defaults means either nothing caches (every request hits the origin, you gained nothing) or everything caches too long (users see stale content for days). Decide a TTL per content type, on purpose.
Heavy compute at the edge. Edge runtimes have tight CPU and time budgets. Pushing a full render, big dependencies, or chatty database calls to the edge gives you the *worst* of both worlds: slow functions far from your data. Keep edge logic small and stateless.
Forgetting the `Vary` header. If a response legitimately differs by Accept-Encoding, Accept-Language, or auth, but you don't tell the CDN, it will serve one cached variant to everyone. Set Vary so the cache key includes what actually matters.
Purging everything on every deploy. It feels safe, but it cold-starts the entire cache and stampedes your origin. Use hashed filenames so deploys need no purge at all.
Takeaways
The whole article in seven lines
A CDN serves copies of your content from PoPs near the user; a cache hit never touches your origin.
Edge compute runs small, stateless code at those PoPs for fast decisions close to the user.
`Cache-Control` headers, `max-age`, `public`/`private`, `stale-while-revalidate`, `immutable`, *are* your caching policy.
Cache what's the same for everyone; compute per-user logic at the origin.
Invalidate by TTL expiry, cache busting (hashed URLs), or active purge, in that order of preference.
The edge wins for static, public, read-heavy content; the origin owns dynamic, private, write-heavy work.
The edge isn't a place to move your backend, it's a place to answer the easy questions before the hard ones reach home.
Where to go next
The edge is one layer of a fast, global system. Two siblings pair naturally with this one: the gateway that sits in front of your services, and a real-world case study of edge-scale delivery.
API Gateways & the Edge, the control point that authenticates, routes, and rate-limits requests as they enter your platform.
This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.