← All posts

Why Caching Is the Cheapest Speed Boost You'll Get

If I could give one piece of performance advice to anyone running a website or an API, it'd be this: cache things. Not because it's fancy, but because it's the closest thing to a free lunch in web development. The fastest request is the one your server never has to handle.

What Is Caching, Really?

Caching just means keeping a copy of something expensive to produce so you can hand it out again without redoing the work. That "something" might be a rendered page, a database query result, an image or an API response.

Think of it like keeping your most-used spices next to the stove instead of walking to the pantry every time. Same food, way less walking.

Why Bother?

Here's what you get when you cache well:

  • Speed. Serving a stored copy takes milliseconds. Hitting the database, running some logic and rendering a response takes a lot longer.
  • Lower costs. Fewer requests reaching your origin means a smaller server, fewer database reads and a lower cloud bill.
  • Resilience. If your database has a bad five minutes, a warm cache can keep serving users while you fix it.
  • Handling traffic spikes. When a post hits the front page of Hacker News, cached responses absorb the load instead of your server melting.

The Layers of Caching

Caching isn't one thing. It happens at several layers, and they stack nicely.

1. The Browser

The browser can keep files locally so repeat visits don't download them again. You control this with HTTP headers:

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

That tells the browser "this file won't change for a year." It's perfect for fingerprinted assets like app.3f9a1c.js, where a new build gets a new filename.

2. The CDN or Reverse Proxy

This is the big one. A CDN stores copies of your content on servers around the world, so visitors get responses from somewhere nearby. If you read my post on reverse proxies, this is the same idea, and a free Cloudflare setup already gets you most of the way there.

For content that changes now and then, you can let the CDN hold a copy briefly:

Cache-Control: public, s-maxage=300, stale-while-revalidate=60

Translation: the CDN can serve this for five minutes, and for a bit after that it can serve the old copy while quietly fetching a fresh one. Users never wait.

3. The Application

Inside your app, you can cache the results of slow work, like a heavy database query or a call to a third-party API. Here's a tiny in-memory example in TypeScript:

const cache = new Map<string, { value: unknown; expires: number }>();

async function cached<T>(key: string, ttlMs: number, load: () => Promise<T>): Promise<T> {
	const hit = cache.get(key);
	if (hit && hit.expires > Date.now()) return hit.value as T;

	const value = await load();
	cache.set(key, { value, expires: Date.now() + ttlMs });
	return value;
}

That's enough for a single-server side project. Once you run multiple instances, you'll want a shared store like Redis so they all see the same cache.

4. The Database

Databases cache too (query plans, hot pages in memory), and you can help by adding the right indexes. It's not glamorous, but it's caching's quiet cousin.

Caching for APIs

APIs benefit enormously, especially read-heavy ones. A few habits worth adopting:

  • Send Cache-Control headers on GET responses so clients and CDNs know what's safe to reuse.
  • Use ETag or Last-Modified so clients can ask "has this changed?" and get a tiny 304 Not Modified instead of the whole payload.
  • Cache upstream calls. If you depend on someone else's API, caching their responses keeps you under rate limits and keeps you working when they're down.
  • Don't cache private data publicly. Anything user-specific should be private or skipped entirely.

The Gotchas

There's an old joke that the two hard problems in computer science are naming things, cache invalidation and off-by-one errors. It's a joke because it's true.

Stale data. The more aggressively you cache, the longer people might see old content. Pick a time-to-live (TTL) that matches how much staleness you can tolerate. A blog post can be cached for hours. A stock price, not so much.

Caching the wrong thing. Cache a logged-in user's dashboard on a shared CDN and you'll show one person's data to another. Always double-check what varies per user.

Stampedes. When a popular entry expires, hundreds of requests can slam your origin at once. Techniques like stale-while-revalidate or locking the refresh help a lot.

Cache-busting. For files that change, use versioned filenames rather than hoping everyone clears their cache.

Where to Start

You don't need a grand strategy. Do this, in order:

  1. Put long-lived Cache-Control headers on your static assets.
  2. Put a CDN in front of your site.
  3. Find your slowest, most repeated query or API call and cache it with a sensible TTL.
  4. Measure again. Repeat if it's still slow.

Caching won't fix bad code, but it makes good code feel instant and keeps your infrastructure bill pleasantly small. Start with the easy layers, be honest about how fresh your data needs to be, and you'll get a faster, sturdier site without rewriting anything.