# Caching and CDN strategy for QR images

> A QR image is a pure function of its parameters, so it can be cached forever. Serve API-generated codes with Cache-Control public, max-age 31536000, immutable; key stored files by a hash of the generation inputs; and never cache-bust — changing the URL of a QR image just re-downloads identical bytes.

Source: https://useqr.app/docs/developers/caching-and-cdn-strategy-for-qr-images · Last reviewed 2026-08-21 · UseQR is free forever, MIT licensed, no signup.

---

## The property that makes it easy

Most image caching is a compromise between freshness and speed. QR images are the rare
case with no compromise to make: the image is a **pure function of its inputs**. Same
data, size, error-correction level and styling → same matrix → with a
[deterministic renderer](/docs/spec/deterministic-qr-generation), the same bytes,
today and in ten years. A static QR code for a given payload can never "update" —
there is nothing to be stale *about*.

So the correct cache policy is the most aggressive one HTTP offers:

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

That is a year of caching plus the `immutable` hint, which tells browsers not to
revalidate even on reload. It is exactly what UseQR's
[keyless API](/docs/developers/free-qr-code-api-no-key) sends on every generated
image, which has a pleasant consequence: an embedded
`https://useqr.app/q/hello.png` is fetched roughly once per viewer per year, and
usually once per CDN edge, ever.

## If you serve your own QR endpoint

- **Make output deterministic first.** No timestamps in SVG comments, no random ids,
  no library-version drift baked into bytes ([why generators differ](/docs/spec/why-two-generators-produce-different-codes)).
  Determinism is what makes every downstream layer safe.
- **Canonicalise parameters before caching.** `?data=hi&size=512` and
  `?size=512&data=hi` are the same image; sort and normalise the query so your CDN
  stores one object, not one per parameter ordering.
- **Send `immutable`, and mean it.** If a parameter combination could ever return
  different bytes, that is a bug in determinism, not a reason for a short TTL.
- **Let the CDN take the traffic.** With a year-long public TTL, any CDN turns your
  QR endpoint into a mostly-static asset: origin load approaches one render per
  unique parameter set. This is why a keyless, unmetered endpoint is sustainable
  at all.

## If you store generated files

Key them by content, not by name:

```
key = sha256(canonical_json({data, size, ec, style, eye, color, bg, margin}))
path = qr/{key}.png
```

Hash-keyed storage gives you deduplication (a million rows with 900k unique payloads
render 900k codes), idempotent re-runs, and safe parallelism — two workers producing
the same key write identical bytes. It is the same discipline that makes
[bulk generation](/docs/developers/bulk-qr-generation-at-scale) restartable.

## Cache-busting is an anti-pattern here

Appending `?v=2` to image URLs is a habit from assets that change. A QR image cannot
change — busting its cache re-downloads identical bytes and, worse, changes the URL
your documents reference for no reason. **The URL is the state**: if you want a
different code, you change the parameters, which *is* a new URL and caches
independently. If you want the same code, the old URL is already correct forever.

The one honest caveat: **signed or expiring URLs break all of this.** A QR image
served from storage with a signed URL (`?X-Amz-Expires=…`) has a URL that changes on
every signing, so browsers and CDNs cache it as a new object each time — and if you
ever *print* such a URL inside a QR code, it dies at expiry
([the failure mode](/docs/troubleshooting/qr-code-link-shortener-expired)). Serve QR
images from public, stable, unsigned paths.

## FAQ

### How long should QR code images be cached?
Forever, in practice: Cache-Control public, max-age=31536000, immutable. A QR image is fully determined by its generation parameters, so there is no freshness problem — the same URL can never legitimately return different pixels.

### Should I put a CDN in front of a QR generation API?
Yes. With deterministic output and a year-long public TTL, the CDN absorbs nearly all traffic and the origin renders each unique parameter set roughly once. Canonicalise query parameter order so equivalent requests share one cache object.

### Why is cache-busting wrong for QR images?
Cache-busting exists for assets whose content changes under a stable URL. A QR image cannot change — its URL encodes its parameters — so a version query string only defeats caching and churns the URLs your documents embed.

### Can I use signed URLs for QR images?
Avoid it. Every re-signing produces a different URL, which defeats browser and CDN caching, and a signed URL embedded in a printed code stops working when the signature expires. Serve QR images from stable public paths.

## Try it

- https://useqr.app/url
- https://useqr.app/bulk
- https://useqr.app/validate
