Developers & agents
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.
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, 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 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). Determinism is what makes every downstream layer safe.
- Canonicalise parameters before caching.
?data=hi&size=512and?size=512&data=hiare 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 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). 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 — free, no signup
Related
- A free QR code API with no key — UseQR's REST API needs no signup, no API key and no SDK. GET /api/v1/qr?data=hello returns a PNG. The shortest form is /q/hello.png, which drops straight…
- Bulk QR generation at scale — Generating hundreds to millions of QR codes: the batch API and its limits, local generation with worker pools, determinism as a caching strategy, and manifests.
- QR code generation performance — What generating a QR code actually costs: encoding is microseconds, rendering dominates, and PNG rasterisation is the expensive step. Where optimisation pays.
- QR codes in serverless functions — Generating QR codes in Lambda, Cloudflare Workers and Vercel functions — bundle size, native dependency pitfalls, streaming, and when to skip the function entirely.