All projects games/01 · real time

SOMS

A snippet of a song plays and everyone races to name the title, the artist and the feature before the others. At the end, a stats card to drop in the group chat.

Year
2026
Role
From the PRD to deployment
Stack
Next.js 15 · Fastify + Socket.IO · Prisma · Neon · Upstash · Deezer
Link
code on GitHub ↗
Tela inicial do SOMS: o nome do jogo em letras grossas, o mote todo mundo acha que sabe logo abaixo, e um cartão perguntando qual seu apelido.
the front door: a nickname, a room code and you are in hover to see it in colour

The problem

Everyone has played name-that-tune around a table, and everyone has watched it fall apart: someone shouts first, nobody knows who actually got it first, and the argument about who spoke first lasts longer than the round.

An automatic scoreboard solves that, as long as it can say without doubt who got there first.

The decision

The real-time server lives outside Vercel. Serverless functions do not hold a long TCP connection. There are paid workarounds, but Fastify with Socket.IO in a Railway container costs about five dollars a month past the free tier and gives me full control of the protocol. The site stays on Vercel and only the game lives elsewhere.

All scoring is decided on the server, which is the one that knows the exact moment each answer arrived.

The URLs die in thirty minutes

Each song snippet comes from Deezer, and its URL is not a stable address: it is a signed link with an expiry baked in, in Akamai’s token format. Storing that URL in the database works in testing and fails in production a few hours later, when nobody is looking at that code any more.

Instead of guessing, I measured: of twenty cached tracks, twelve were answering 403 forty minutes later. In a real match that would be most of the rounds playing silence.

The fix separated what has a long shelf life from what does not. The database stores the track metadata, which is stable. The snippet URL is fetched again, in a batch, the moment the room starts a match, before the countdown and with rate limiting so it never blows the API’s ceiling. If a track has vanished from the catalogue in the meantime, it is dropped there and replaced before any player notices. That "Preparing match..." on screen is that batch happening.

~30minof validity on the signed URL
12 of 20dead tracks after 40 minutes
300ms to 2sthe whole batch before the countdown

Getting it "almost" right still counts

Nobody types a song title exactly as it appears in the catalogue, with the accents, the "(Remastered 2011)" on the end and the featured artist in the right order. So the answer goes through normalisation and a table of aliases before it is compared, and the scoreboard accepts the way people actually write.

A bug that only exists after half an hour

The signed-URL bug would pass any test suite, because at minute zero everything is green. It only shows up half an hour later and only on the machine of whoever is playing. Since then I look at every third-party API integration asking how long that stays valid, and not only whether it answers correctly right now.