Back to Blog
Guide10 min read2026-07-09

How to Set Up CDN and Caching for Static Sites

A static site is only as fast as its caching. Here's how CDNs, cache-control headers, and content hashing work together — and the exact header strategy that gives you instant loads and safe deploys.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Why caching is the whole game for static sites

A static site is just files: HTML, CSS, JS, images, fonts. The performance question is entirely *where those files live relative to the user* and *how long browsers and edges keep them*. Get caching right and your site loads instantly worldwide. Get it wrong and you either serve stale code after a deploy or make every visitor re-download everything on every page.

There are two layers to think about:

  1. 1The CDN — copies of your files cached at edge locations close to users.
  2. 2HTTP cache headers — instructions that tell the CDN and the browser how long to keep each file.

How a CDN works

A CDN (Content Delivery Network) is a global network of edge servers. When a user in Singapore requests your site, they hit the nearest edge instead of your origin in, say, Virginia. On a cache miss, the edge fetches from origin once and caches it; every subsequent request in that region is a cache hit served locally — single-digit milliseconds instead of a transcontinental round trip.

The metric you care about is cache hit ratio: the percentage of requests served from the edge. For static assets, you want this close to 100%.

The header strategy that actually works

The trick is treating two kinds of files differently:

Hashed assets: cache forever

Modern build tools (Vite, webpack, Next, Astro) emit assets with a content hash in the filename: app.4f3a9c2.js. The hash changes whenever the content changes, so the URL is effectively immutable. These you cache as aggressively as possible:

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

max-age=31536000 is one year. immutable tells the browser not to even revalidate. When you deploy new code, the filename's hash changes, so it's a *new URL* — no staleness possible.

HTML: never cache (or revalidate every time)

index.html references the hashed assets, so it must always be fresh — otherwise users get an old HTML pointing at assets that no longer exist. Set:

Cache-Control: no-cache

no-cache is slightly misleading: it means "cache, but revalidate with the origin before using." The browser sends an ETag/If-None-Match and gets a cheap 304 Not Modified when nothing changed. For HTML, this is the safe default.

The full picture

File typeExampleCache-ControlWhy
Hashed JS/CSSapp.4f3a9c2.jspublic, max-age=31536000, immutableURL changes on content change
Images/fonts (hashed)logo.a1b2.svgpublic, max-age=31536000, immutableSame
index.htmlindex.htmlno-cacheMust point at current assets
robots.txt, sitemap.xmlpublic, max-age=3600Changes rarely, not catastrophic if stale

Cache invalidation on deploy

The two-tier strategy above mostly avoids the hardest problem in computing — cache invalidation — because hashed URLs are self-invalidating. But your *origin* and any non-hashed files still need a purge on deploy. Most CDNs expose a purge API:

# Cloudflare: purge everything on deploy (simple, slightly wasteful)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything":true}'

Better platforms purge automatically on deploy so you never think about it.

Compression and modern formats

Caching is half the story; payload size is the other half.

  • Brotli compression beats gzip for text assets (HTML/CSS/JS), often 15-25% smaller. Serve Brotli where supported, gzip as fallback.
  • Image formats: prefer AVIF/WebP over JPEG/PNG. They're dramatically smaller at equivalent quality.
  • HTTP/2 or HTTP/3 multiplexes requests over one connection, so many small hashed assets aren't the penalty they were under HTTP/1.1.
Content-Encoding: br
Vary: Accept-Encoding

The Vary: Accept-Encoding header is important — it tells the CDN to cache separate copies per encoding so a Brotli-incapable client doesn't get a Brotli body.

How this works on a managed static host

If you host static sites on a platform, most of this should be automatic. On PandaStack, static builds run in pandastack.ai microVMs (any framework — React/Vite, Next export, Astro, Gatsby, Eleventy, VitePress, Hugo, plain HTML; the platform auto-detects build and output). Sites are served behind Cloudflare DNS with edge caching, automatic SSL on custom domains, and 100GB bandwidth/month on the Free tier (500GB on Pro, more above). The hashed-asset/no-cache-HTML split is the sane default your build tool already produces, and CDN purges on deploy mean you don't manually invalidate anything.

Verifying your caching

Don't trust, verify. Check headers with curl:

curl -sI https://yoursite.com/assets/app.4f3a9c2.js | grep -i cache-control
# expect: cache-control: public, max-age=31536000, immutable

curl -sI https://yoursite.com/ | grep -i 'cache-control\|cf-cache-status'
# HTML expect: no-cache; and a CDN status header showing HIT after warm-up

Look for a HIT status on repeated requests to confirm the edge is serving. Run a Lighthouse audit and check "Serve static assets with an efficient cache policy" — it'll flag anything you missed.

Common mistakes

  • Caching HTML aggressively. The classic "my deploy didn't show up" bug. HTML must revalidate.
  • No content hashing. Without hashes you're forced to choose between stale assets and no caching. Let your build tool hash.
  • Forgetting Vary. Causes wrong-encoding bodies served to clients.
  • Not measuring hit ratio. If your CDN isn't reporting high hit rates on assets, something's misconfigured.

Bottom line

Hash your assets and cache them for a year as immutable; keep HTML on no-cache; compress with Brotli; serve from a CDN close to users; and purge the origin on deploy. That combination gives you near-instant repeat loads with zero risk of serving stale code.

References

  • [MDN — Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
  • [MDN — HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
  • [web.dev — Love your cache](https://web.dev/articles/love-your-cache)
  • [Cloudflare — What is a CDN?](https://www.cloudflare.com/learning/cdn/what-is-a-cdn/)

Want a static site on a global CDN with automatic SSL and cache purging on every deploy? PandaStack's free tier includes static hosting — try it: [dashboard.pandastack.io](https://dashboard.pandastack.io)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also