DEV Community

Cover image for My short link service counted 1 in 5 clicks — 301 caching was eating the rest
InApp
InApp

Posted on Originally published at imapp.blogspot.com

My short link service counted 1 in 5 clicks — 301 caching was eating the rest

I run a small short link service on freeq.one. Agents use it because a short URL should outlive the process that created it — links issued by my bot survive restarts, and the owner gets a one-time manage secret that unlocks click stats and deletion. It started life as a hack for my own bots' unwieldy URLs, and I ended up packaging it as https://x402.freeq.one/tools/shortlink_create.html.

Launch week, the stats made no sense. A test link pointing at a sinkhole on my own box recorded 9 clicks while the destination logged 38. Almost four of every five clicks were invisible.

The culprit: I was answering with 301 Moved Permanently. It felt correct — these are permanent links. But a 301 tells clients to cache the redirect itself. Browsers honor it well past the session, and plenty of HTTP stacks with local caches behave the same. From the second click onward, the client never touches my server — it re-resolves the cached redirect locally. No hit, no stat. Worse, when an owner updated a destination via the manage secret, part of the audience kept landing on the old target until their cache expired or they switched clients.

The fix was one status code: 302 instead. A 302 isn't treated as a permanent answer, so every click comes home through my server. Recorded clicks now match the destination's access log within rounding, and destination edits take effect on the very next click instead of "whenever caches feel like it."

The trade-off is one extra round trip on repeat clicks from the same client. For a link whose entire value is counted traffic plus an editable destination, that's the right price. My rule of thumb now: 301 is for URLs you never intend to count or change; 302/307 when you need the hit or the ability to steer later.

One follow-up gotcha: intermediaries will cheerfully cache even a 302 for an hour and hand it to everyone, flattening per-client stats for that window. I set conservative cache headers on the redirect response itself. Redirects aren't just a status code — they're a contract with every cache in between.

Top comments (0)