Thirty days, 35 browser games, one share button per game, and the referrer breakdown in Cloudflare Web Analytics contained no social host at all. Not a low number. Nothing. The button was not broken in any way that showed up: it opened the OS share sheet on the machine we developed on, it wrote to the clipboard, and the console stayed clean.
That gap — the button works, the loop does not — turned out to have two unrelated causes stacked on top of each other. One is a measurement problem that makes "zero referrers" much less informative than it looks. The other is a real bug that silenced the button on the platform where most of the traffic is. We fixed both. We have not yet measured whether the fix produces shares, and the last section says so plainly.
What "zero social referrers" actually measured
The Referer header is a weak instrument for exactly this question. X wraps outbound links in t.co and its mobile apps commonly send no referrer at all. In-app webviews — Instagram, TikTok, most embedded browsers — frequently omit it. Every share that lands in a DM, an iMessage thread, a WhatsApp group, or a Discord channel arrives as direct traffic. Those are the places a game link actually travels.
So an empty social row is consistent with two very different worlds:
- Nobody shared anything.
- People shared, and the referrer was stripped before it reached us.
We could not tell those apart, which means the 30-day figure that started this work was never evidence that the share loop failed. It was evidence that we had no way to observe it. If you are looking at a similar dashboard, resolve that first, because the two worlds call for opposite responses.
The only fix is to put an identifier inside the payload you hand to the user, not to rely on what the receiving platform decides to forward. We append a short suffix to the shared URL. That creates a real tension the rest of this article has to work around: the thing that makes a share measurable is a link, and a link is the part platforms downrank and users skip.
Why an emoji grid is a different object than a score sentence
Our original share text was a templated sentence — the game name, the score, an exclamation mark, and a URL. Compare that to the format everyone started copying after Wordle's grid spread through January 2022: six lines of colored squares and no link.
The grid is not better copy. It is a different kind of object, and four properties do the work:
- It carries its own rendering. The squares are plain Unicode (U+1F7E9 and friends, added in Emoji 12.0 in 2019) and ship in the system fonts on iOS, Android, Windows and macOS. Pasted into any client, it renders. No Open Graph fetch, no crawler, no card, no dependency on the platform choosing to unfurl your domain.
- It is spoiler-safe. It shows the shape of an attempt without the answer, so posting it costs the sender nothing socially.
- It is comparable. Every player who posts one that day is describing the same puzzle on the same axis. That turns a post into a thread — the reply is "4/6 here," not silence.
- It is reproducible. The format is identical across senders, so it reads as a convention rather than as an ad.
"I scored 4,820 in Tile Cascade!" fails all four. It renders as a link card or as nothing. It has no shared reference frame — 4,820 against what? There is no reply available except congratulations. And a bare number next to a URL is the exact shape of promotional copy, so it gets read as promotional copy.
The distinction that mattered for us: a grid encodes a comparable state, a sentence encodes a claim. Claims need the reader to trust the sender. Comparisons only need a shared axis.
Which exposes the part that is not a formatting change at all. Our 35 games were free-play with random seeds. There is no shared axis, so there is nothing for a grid to encode — any grid we generated would have been decoration on a claim. Shipping the format required shipping a daily seed first: one deterministic instance per game per UTC day, derived from the date so every player gets the same board. That is an architectural change to the game loop, not a change to a share button.
Do not
awaitanything between the click handler andnavigator.share(). The Web Share API requires transient user activation. Chromium's activation window is about five seconds, so a quick fetch usually survives it on desktop Chrome. Safari is stricter — a singleawaitbefore the call is enough to lose activation, andshare()rejects withNotAllowedError. If your catch block falls through tonavigator.clipboard.writeText(), that call needs activation and document focus too, so it fails for the same reason and you get a button that does nothing at all.
The bug that silenced the button on iOS Safari
Our handler minted the share identifier server-side. The sequence was: click, await fetch('/api/share-id'), build the string, call navigator.share(). On desktop Chrome this worked, which is why it shipped.
On iOS Safari it did not. The await cost us the transient activation, share() rejected, the fallback clipboard write rejected for the same reason, and the catch block was empty. No sheet, no toast, no console error a user would ever see. Mobile is where a game gets shared, so the loop was effectively dead on the platform that mattered while looking healthy in development.
The fix has three parts, and none of them are clever:
- Derive the share identifier client-side from the daily seed and the result. No network call in the handler, so nothing to await.
- Feature-detect with
navigator.canShare?.({ text })before calling, and fall back deliberately rather than by exception. Desktop Firefox has nonavigator.shareat all — check current support rather than trusting this sentence. - Make the fallback visible: render the text in a selectable block with an explicit copied state, instead of a silent clipboard write that can fail without telling anyone.
What we changed, and what we have not measured
Shipped: a UTC daily seed per game, a grid-style result block built from the day's board, share text that leads with the grid and puts the URL on its own last line, a synchronous share handler, and a visible copy fallback.
Not measured: whether any of it produces referrers. This article is the diagnosis and the change, not the result. We also did not test Android Chrome Custom Tabs, did not verify how the fallback link renders as a Bluesky card, and have not checked whether the daily seed reduces total sessions — capping a free-play game at one board a day is a real cost, and it is plausible the trade is negative.
If you cannot ship a shared daily instance, do not copy the grid. Ship a decent per-game og:image and accept ordinary link-card sharing; a grid with no common axis is noise with extra steps. The condition that flips it is exactly that axis — the moment every player is solving the same thing on the same day, the format starts doing work that copy cannot.
And given how much of this came down to not being able to observe our own traffic: a channel you own answers the question directly. Referrers are optional; a subscriber list is not.
Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.
Top comments (0)