Caching, CDNs, and Speed
Table of Contents
- The Fastest Request Is the One You Don't Make
- Cache-Control: How Long to Keep It
- Checking Back: ETags and 304
- Why Your Change Isn't Showing Up
- Fingerprinted Files
- CDNs: Copies Close to You
- Compression
- Other Ways to Be Fast
- Try It Yourself
1. The Fastest Request Is the One You Don't Make
Think back over the last few chapters. Every request costs a DNS lookup (maybe), a connection (maybe), at least one round trip, and the time to download the response. Multiply that by the dozens of requests on a page, and by every page a person visits.
But most of those requests ask for things that haven't changed. Your
site's style.css is the same on the home page, the about page, and the
contact page — and the same today as yesterday. Downloading it again
every time is pure waste.
A cache is a saved copy of something, kept so you don't have to fetch it again. You met caches in How Computers Work — the CPU's caches in front of RAM — and in chapter 3, where every step of DNS remembered answers. The web uses the same trick, in several places:
Browser cache ──▶ CDN cache ──▶ Server
(on your device) (near you) (the original)
If the browser has a fresh copy, the request never leaves your computer. If not, maybe a nearby CDN has one. Only if neither does, the request goes all the way to the original server.
2. Cache-Control: How Long to Keep It
The server decides how long a response may be cached, using the
Cache-Control response header. You saw it in chapter 6:
cache-control: max-age=600
That means: "This response stays fresh for 600 seconds (10 minutes). Until then, don't ask me again — just use your copy." For those ten minutes, the browser loads your page instantly from its own cache, without any network request at all.
The values you'll see most:
| Value | Meaning |
|---|---|
max-age=600 | Fresh for 600 seconds |
max-age=31536000, immutable | Fresh for a year — this file will never change (section 5) |
no-cache | You may keep a copy, but check with me before every use (section 3) |
no-store | Don't keep a copy at all — for private things like a bank statement |
private | Only the user's own browser may cache it, not a shared CDN |
public | Anyone may cache it, including CDNs |
The names are confusing: no-cache does not mean "don't cache".
It means "always check first". The one that really means "don't cache"
is no-store.
3. Checking Back: ETags and 304
What happens when a copy is no longer fresh? The browser doesn't have to download the whole thing again. It can ask whether it changed.
Remember the etag header from chapter 6:
etag: "6aa7ac7d-60b"
An ETag is a label for one exact version of a file — like a
fingerprint. When the browser's copy goes stale, it sends the label
back in an If-None-Match header:
GET /my-site/style.css HTTP/1.1
Host: dara-sok.github.io
If-None-Match: "6aa7ac7d-60b"
If the file hasn't changed, the server answers with a tiny response and no body:
HTTP/1.1 304 Not Modified
"Your copy is still good — use it." That's the 304 from chapter 6. It
still costs a round trip, but it skips downloading the file. If the file
has changed, the ETag won't match, and the server sends a normal
200 with the new version.
There's an older version of the same idea that uses dates: the server
sends Last-Modified, and the browser asks with If-Modified-Since.
4. Why Your Change Isn't Showing Up
Here's caching causing a problem you've probably already met. In the Git series, you merged a pull request, GitHub Pages rebuilt your site… and when you opened it, you still saw the old version.
Look at the header again: cache-control: max-age=600. GitHub Pages
tells every browser to keep pages for up to 10 minutes without
checking. Your browser was doing exactly what it was told.
When you need to see the latest version right now:
- Hard reload: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS). This tells the browser to ignore its cache for this page.
- In DevTools → Network, tick Disable cache. While DevTools is open, nothing is cached. Most web developers leave this on while working.
- Open the page in a private/incognito window, which starts with an empty cache.
"Have you tried a hard refresh?" is one of the most common questions in web development. Now you know why.
5. Fingerprinted Files
Caching creates a dilemma. Long cache times make sites fast. But if a file is cached for a year, how do you ever update it?
The answer is to change its name whenever its content changes. Build tools add a short fingerprint — a hash of the file's content — to the file name:
style.css → style.3f9a1c2b.css
app.js → app.81d0e47f.js
Now each file can be cached for a year (max-age=31536000, immutable),
because a file with that exact name will never change. When you
edit the CSS, the build produces style.5c2e09aa.css instead, the HTML
points to the new name, and browsers fetch it as a brand-new file. The
HTML page itself gets a short cache time, so it's always pointing at
the latest names.
This website works this way. Open DevTools on any page here and look at
the files under /_next/static/ — you'll see names full of random-
looking characters. That's Next.js fingerprinting them at build time.
6. CDNs: Copies Close to You
Chapter 4 showed that distance costs time: about 40 ms per round trip to Singapore, 250 ms to the United States. A server in one country is slow for everyone far away from it.
A CDN — Content Delivery Network — fixes this by keeping copies of a site on hundreds of servers around the world, called edge servers. DNS (chapter 3) sends each visitor to a nearby one. Someone in Phnom Penh gets the copy in Singapore; someone in Paris gets the copy in Paris.
Original server (USA)
│
┌────────────┼────────────┐
Edge: Singapore Edge: Paris Edge: São Paulo
│ │ │
Visitors in Asia in Europe in South America
The first visitor near an edge server causes a cache miss: the edge fetches the file from the original and keeps a copy. Everyone after that gets a cache hit, served from nearby.
You've been using a CDN all along. GitHub Pages runs on one, called Fastly. That's what these headers from chapter 6 were about:
x-cache: HIT
age: 245
x-served-by: cache-sin-…
x-cache: HIT means the edge server had a copy. age: 245 means that
copy is 245 seconds old. And sin is the airport code for
Singapore — the edge server that answered. CDNs like Cloudflare,
Fastly, and Amazon CloudFront are a big part of why this website (and
most of the web) feels fast wherever you are.
7. Compression
Text compresses extremely well. HTML, CSS, JavaScript, and JSON usually shrink by 70–80% when compressed — a 100 KB script becomes 25 KB on the wire.
Remember the Accept-Encoding request header from chapter 5:
Accept-Encoding: gzip, br
The browser is saying: "I can unzip gzip and Brotli." The server compresses the response and says which one it used:
Content-Encoding: br
The browser unzips it before parsing. You never see it happen. Images and videos are already compressed in their own formats (JPEG, WebP, MP4), so servers don't bother compressing those again.
8. Other Ways to Be Fast
Caching, CDNs, and compression are the big three. A few more you'll hear about as a frontend developer:
- Fewer requests: every request has a cost, so combine small files and avoid loading things the page doesn't use.
- Smaller images: images are usually the heaviest part of a page. Resize them to the size they're shown at, and use modern formats like WebP. The photos on this website are kept under about 1600px wide for exactly this reason.
- Lazy loading:
<img loading="lazy">only downloads an image when you scroll near it. - Measure: Chrome's Lighthouse tab in DevTools gives a page a performance score, with a list of what to fix.
9. Try It Yourself
-
See the cache in action. Open your GitHub Pages site with DevTools → Network open, and make sure Disable cache is off. Load the page, then click a link and come back. Look at the Size column: you'll see
(memory cache)or(disk cache)instead of a number. Now tick Disable cache and reload — real sizes again. -
Make a 304 yourself. Get your page's ETag, then send it back:
curl -I https://dara-sok.github.io/my-site/ curl -I https://dara-sok.github.io/my-site/ \ -H 'If-None-Match: "6aa7ac7d-60b"'Replace the ETag in the second command with the one you got — quotes included. The second response should be
304. -
Watch the CDN. Run
curl -Ion your site twice, a few seconds apart. Comparex-cache,age, andx-served-by. Is theagegoing up? -
Measure compression.
-w "%{size_download}\n"prints how many bytes were downloaded, and--compressedasks for a compressed response:curl -s -o /dev/null -w "%{size_download}\n" https://github.com curl -s -o /dev/null -w "%{size_download}\n" --compressed https://github.comHow much smaller is the second number?
-
Time your next change. Next time you merge a change to your site, note how long until a normal reload shows it — and whether a hard reload shows it sooner.
You've now seen everything that happens for a simple site. In the next chapter, we'll look at how that changes for web apps and APIs.
Buy Me a Coffee
Found this tutorial helpful? Consider buying me a coffee to support more free content!