What I learned from building Klink alone
A drinking-game web app that was supposed to be a weekend project, and turned into a lesson in scope, architecture and actually shipping something finished.

Klink was supposed to be a weekend project. One evening on the couch with an idea for a Norwegian drinking game in the browser, a domain that was available (klinkn.no — klink.no was taken), and the notion that I could ship something "quickly". Six months later the app is in production, has PWA support, a Hot Seat timer, custom game packs and an easter egg triggered by tapping the logo ten times.
Here is what I learned along the way. None of it is particularly original — but these are things I now actually know, rather than just nod along to when someone else writes them.
Scope is the most important decision
The first mistake I made was thinking: "it's just a drinking game, this is simple". It isn't. Even a "simple" product has hundreds of micro-decisions — how player names get interpolated into cards, how you handle a player who drops out halfway through, how you share a game via QR code without creating user accounts.
What I did right was committing early to a few deliberate constraints:
- No user accounts. Player state lives in sessionStorage. You open the app, add players, play, close the tab. No DB writes per session, no auth, no GDPR overhead.
- No multiplayer. One device, passed around between the players — exactly like a physical deck of cards.
- One language. Norwegian. Klink is a Norwegian concept for a Norwegian context. i18n is a chore I don't need right now.
Each of these decisions eliminated weeks of work. The most important skill on a solo project isn't building fast — it's saying no to things that don't belong yet.
Server-first is mostly right, but not always
I work server-first by default: Next.js App Router, server components, data on the server, client components only when needed. It's almost always the right call.
Klink is one of the rare cases where it isn't right for everything. The card logic (shuffle, next card, interpolating names) runs entirely in the client with React Context, because:
- The game has to feel instant. No latency between "tap next" and "new card appears".
- It has to work offline. PWA support means the app must be able to run without a network.
- It isn't sensitive. Who gets which card can leak to the client — it is the client.
What I use the server for is fetching game packs and cards from Supabase, and that's ISR-cached with a long revalidation time. I like this split: knowledge on the server, behaviour on the client.
Fisher-Yates, and why Math.random is good enough
A drinking game needs the cards to be shuffled. The first version used array.sort(() => Math.random() - 0.5), which is a classic antipattern — it isn't uniform, and some cards statistically end up early or late in the deck more often than they should.
I switched to Fisher-Yates:
function shuffle(array) {
const result = [...array];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}I briefly considered using crypto.getRandomValues() to get cryptographic randomness. Then I reminded myself that this is a drinking game. Math.random() gives you biased bits in the fourth decimal place. Nobody is going to notice.
A principle I try to remember: the solution should be as robust as the problem requires, not as robust as the problem could conceivably require.
The design is the difference
Klink isn't the only Norwegian drinking game online. There's one called Børst that is well built and has been around for a long time. I spent time choosing a visual identity that couldn't be confused with it:
- Lime and dark green as the dominant colours (Børst is blue/orange)
- A sans-serif with character, not something generic
- Glassmorphism with white container cards on coloured backgrounds
- An easter egg — "Athina mode" — triggered by tapping the logo 10 times, which switches the whole app to leopard print + pink. The more little touches like this, the more personality.
There's a paradox here. The technical side of Klink is solid craftsmanship but not impressive — Next.js and Supabase do 90% of the work. The design decisions are what people remember. That taught me to care more about details I would previously have delegated.
The deploy flow that actually holds up
On Eiendomsavtaler.no I built a CI/CD pipeline with stages, tests, staging, approvals, the works. That was the right call there.
On Klink I have: git push origin master. Vercel merges to production if the build passes. That's it. No staging. No manual approval. No "post-deploy smoke test".
This isn't laziness — it's the right size for the problem. Klink has one developer, no SLA, no customers bleeding money if the site is down for ten minutes. The complexity of a CI pipeline has to be proportional to the cost of a failure.
What I would have done differently
- Written the database schema on paper first. I changed the game pack and card tables four times because I hadn't thought through the relations. Supabase migrations are easy enough to roll out, but it eats time.
- Built an admin panel earlier. I edited cards directly in the Supabase dashboard for months. A simple form would have saved me hours.
- Not left the PWA setup until the end. next-pwa is fairly straightforward, but service workers have their own caching rules that clash with Next.js ISR if you don't think it through. Easier to set it up from day one.
Why it was worth it
Klink doesn't have thousands of users. It isn't a business. But it was the first app I shipped from idea to production completely on my own — no team, no customers, nobody to blame if something wasn't good enough.
It taught me that I can deliver an entire product by myself. That's a different kind of confidence than delivering well within a team. Both matter, but you only get the first one by finishing something entirely alone.
You'll find the app at klinkn.no. The code is on GitHub. The code contains a surprise or two.
This text is machine translated and has not been reviewed yet.
Subscribe via RSS or JSON Feed, or get in touch.
Share on: LinkedIn · Redi AS · Spotify · Instagram · Wikidata · GitHub (work) · GitHub (personal)