web / cdn

Content Distribution Networks (CDNs) cache content at edge locations close to users, reducing latency and load on the origin server.

Architecture

CDNs pull content from their origin server during HTTP requests:

DNS -> CDN -> Origin

Example:

Cloudflare DNS -> Cloudflare CDN -> Render

Without a CDN

If a CNAME record points directly to an app server:

www.example.com -> app.onrender.com

Every HTTP request for a static asset:

Logs will show:

200 GET /css/app.css
200 GET /js/app.js

This wastes web processes that should handle application logic, not serve static files. Response times degrade as processes queue up.

With a CDN

The first time a user requests an asset:

200 GET /css/app-a1b2c3d4.css

A CDN cache miss "pulls from the origin", making a GET request to the origin server, storing the result in the CDN cache, and serving the result to the user.

Future GET and HEAD requests to the same URL within the cache duration are served from the CDN cache with no request to the origin.

All HTTP requests using verbs other than GET and HEAD proxy through to the origin.

Cache invalidation

To maximize cache efficiency, set long cache headers (1 year) and change the asset URL when the content changes.

Asset fingerprinting embeds a hash of the file's contents in the URL or filename:

/assets/app-a1b2c3d4.css

When the file changes, the hash changes, creating a new URL. The CDN cache misses and pulls the new version from origin. Old URLs remain cached but are no longer requested.

Cache-Control header:

Cache-Control: public, max-age=31536000, immutable

The immutable directive eliminates revalidation requests even on page reload.

Compression at the edge

Modern CDNs negotiate compression with the client and encode responses on the fly: gzip for older clients, Brotli (br) where supported, sometimes zstd. The origin serves uncompressed bytes; the CDN does the work close to the user.

Application-level gzip middleware is usually redundant, and can be actively harmful if it forces the CDN to skip Brotli, which compresses text better than gzip.

Configure compression on the CDN; let the origin focus on generating responses.

Don't cache errors

CDNs cache based on the Cache-Control header, not the HTTP status code. If the origin returns a 404 with Cache-Control: public, max-age=31536000, immutable, the CDN will cache that 404 for a year.

This matters during rolling deploys. A request for a new fingerprinted asset may hit an old server that doesn't have the file yet. If that 404 carries cache headers, the CDN edge caches the error and serves it to every subsequent user in that geography.

The fix is at the origin: only set long cache headers on 200 responses. Set Cache-Control: no-store on errors so they pass through to origin on the next request.

Implementations

See cmd/esbuild for build-time fingerprinting, or go/fingerprint for in-process fingerprinting in a Go-only server.

← All articles