·Ferret Media

The page ships no JavaScript, and that was the easy part

How a link-in-bio page ends up as one request with an inlined stylesheet — and why the hard problem was counting the visit afterwards.

Open any Linker page and view the source. There is no script tag. Not a small one, not a deferred one — none.

This is less impressive than it sounds, and more interesting than it sounds, for two different reasons.

Why it is less impressive than it sounds

A link-in-bio page is a heading, a picture, some text and a column of buttons. It has no state. It has no navigation. Nothing on it changes after it arrives. The interactive element people expect — a button that lifts slightly under the pointer — is a CSS rule.

So the correct amount of JavaScript for this page was always zero. The unusual thing is not that we got there; it is that the industry default would have shipped a framework, a hydration step and a client bundle to render four anchors, and nobody would have blinked.

The theme works the same way. Every option in the editor — the background, the corner radius, the shadow, the button fill and its transparency, the blur behind it, what a hover does — comes out the other end as a CSS custom property in a style attribute. Choosing a theme is choosing about a dozen numbers. Rendering one is substituting them.

The pleasant side effect is that the dashboard’s live preview and the public page are two components reading the same properties, so what you see while editing is genuinely what ships. There is no second renderer to keep in step, only a stylesheet to agree on.

Why it is more interesting than it sounds

Here is the part that took actual thought: if there is no JavaScript on the page, nothing in the browser can report that the page was viewed.

Analytics, as normally practised, is a script that phones home. Take the script away and the obvious replacement is to count on the server — which we do. The renderer reports each view as it renders.

But that collides with the other thing you want, which is caching. The page used to be cached at the edge with s-maxage=300, and a cached response never reaches the worker. A visit that never reaches the worker cannot be counted. We would have been counting cache misses and calling them people.

So the caching moved down a layer. The HTML is no longer cached; the upstream reads behind it are — the atproto lookups that fetch your identity, your links and your theme. A repeat visit still costs nothing upstream, and the worker still runs, which is what makes the number mean something. Rendering the page was never the expensive part.

The filter is the whole game

The last problem is the one nobody warns you about. A link-in-bio URL exists to be pasted into chat apps, and every one of them fetches the page to build a preview card. Left alone, your “visits” would be mostly Discord, Slack, Telegram and Twitterbot.

So each view goes through four checks. One is a maintained blocklist of crawlers. The other three go the opposite way round: rather than naming what to exclude, they ask for the marks a real navigation leaves — a user agent that at least claims to be a browser, a Sec-Fetch-Dest: document header, and no Sec-Purpose: prefetch.

The bias is deliberately towards under-counting. A number that is slightly low is still usable. A number inflated by preview bots is not, and from the outside you cannot tell which kind you have.

And because the count happens on our side, there is nothing in the page to block, nothing to consent to, and no address stored anywhere. A visitor is a hash of the day, the page, the IP and the user agent — with the day inside the message, so tomorrow they are somebody else entirely.

Your links, in your repo, on a page that loads instantly.

Free at your Bluesky handle, for as long as you like. The paid plan is there when you want the address to be yours too.