Hopp til hovedinnhold
nextjssupabasepwasideprosjekt· 02. april 2026 · 5 min

Hva jeg lærte av å bygge Klink alene

Et drikkespill-webapp som skulle være en helgeprosjekt, og ble til en skole i scope, arkitektur og det å levere noe ferdig.

Hva jeg lærte av å bygge Klink alene

Klink skulle være en helgeprosjekt. Én kveld på sofaen med en idé om et norsk drikkespill i nettleseren, et domene som var ledig (klinkn.no , klink.no var tatt), og en forestilling om at jeg kunne levere noe "raskt". Et halvt år senere er appen i produksjon, har PWA-støtte, en Hot Seat-timer, egne spillpakker og et easter egg som aktiveres av ti trykk på logoen.

Her er det jeg lærte underveis. Ingen av det er særlig originalt , men det er ting jeg nå faktisk vet, ikke bare nikker gjenkjennende til når andre skriver det.

Scope er den viktigste avgjørelsen

Den første tabben jeg gjorde var å tenke: "det er bare et drikkespill, dette er enkelt". Det er det ikke. Selv et "enkelt" produkt har hundrevis av mikroavgjørelser , hvordan spillernavn interpoleres inn i kort, hvordan du håndterer en spiller som dropper ut midtveis, hvordan du deler et spill via QR-kode uten å lage brukere.

Det jeg gjorde riktig var å låse meg tidlig til noen bevisste begrensninger:

  • Ingen brukerkontoer. Spillertilstand bor i sessionStorage. Du åpner appen, legger til spillere, spiller, lukker fanen. Ingen DB-skriving per økt, ingen auth, ingen GDPR-båndbredde.
  • Ingen multiplayer. Én enhet, delt mellom spillerne , akkurat som et fysisk kortspill.
  • Ett språk. Norsk. Klink er et norsk konsept for en norsk kontekst. i18n er et ork jeg ikke trenger nå.

Hver av disse avgjørelsene eliminerte ukevis med arbeid. Den viktigste ferdigheten på et soloprosjekt er ikke å bygge fort , det er å si nei til ting som ikke hører hjemme enda.

Server-first er stort sett riktig, men ikke alltid

Jeg jobber server-first som default: Next.js App Router, server components, data på serveren, klientkomponenter kun når det trengs. Det er nesten alltid riktig valg.

Klink er et av de sjeldne tilfellene der det ikke er riktig for alt. Kort-logikken (shuffle, neste kort, interpolere navn) kjører helt i klienten med React Context, fordi:

  1. Spillet skal føles umiddelbart. Ingen latency mellom "trykk neste" og "nytt kort vises".
  2. Det skal funke offline. PWA-støtte betyr at appen må kunne kjøre uten nettverk.
  3. Det er ikke sensitivt. Hvem som får hvilket kort kan lekke til klienten , det er klienten.

Det jeg bruker serveren til er å hente spillpakker og kort fra Supabase, og det er ISR-cachet med lang revalidation-tid. Jeg liker denne delingen: kunnskap på serveren, oppførsel på klienten.

Fisher-Yates, og hvorfor Math.random er nok

Et drikkespill krever at kortene stokkes. Første versjon brukte array.sort(() => Math.random() - 0.5), som er en klassisk antipattern , den er ikke uniform, og noen kort havner statistisk oftere tidlig eller sent i bunken.

Jeg byttet til 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;
}

Jeg vurderte kort å bruke crypto.getRandomValues() for å få kryptografisk tilfeldighet. Så minnet jeg meg selv på at dette er et drikkespill. Math.random() gir biased bits i det fjerde desimalet. Ingen kommer til å merke det.

Et prinsipp jeg prøver å huske: løsningen skal være så robust som problemet krever, ikke som problemet kunne tenkes å kreve.

Designet er forskjellen

Klink er ikke det eneste norske drikkespillet på nett. Det finnes et som heter Børst som er bygget godt og har vært der lenge. Jeg brukte tid på å velge en visuell identitet som ikke kunne forveksles:

  • Lime og mørkegrønn som dominerende farger (Børst er blå/oransje)
  • Sans-serif med karakter, ikke generisk
  • Glass-morphism med hvite container-kort på fargede bakgrunner
  • Et easter egg , "Athina-modus" , som aktiveres ved 10 trykk på logoen og bytter hele appen til leopard + rosa. Jo flere slike småting, jo mer personlighet.

Det er et paradoks her. Den tekniske delen av Klink er solid håndverk men ikke imponerende , Next.js og Supabase gjør 90% av jobben. Designavgjørelsene er det folk husker. Det lærte meg å bry meg mer om detaljer jeg tidligere ville delegert.

Deploy-flyten som faktisk holder

På Eiendomsavtaler.no bygget jeg en CI/CD-pipeline med steg, tester, staging, godkjenninger, alt. Det var riktig der.

På Klink har jeg: git push origin master. Vercel merger til produksjon hvis build passerer. Det er det. Ingen staging. Ingen manual approval. Ingen "post-deploy smoke test".

Dette er ikke latskap , det er riktig størrelse for problemet. Klink har én utvikler, ingen SLA, ingen kunder som blør penger hvis siden er nede i ti minutter. Kompleksiteten i en CI-pipeline må stå i forhold til kostnaden av en feil.

Hva jeg skulle gjort annerledes

  1. Skrevet database-schemaet på papir først. Jeg endret spillpakker- og kort-tabellene fire ganger fordi jeg ikke hadde tenkt gjennom relasjonene. Supabase-migrasjoner er greie å rulle ut, men det eter tid.
  2. Laget et admin-panel tidligere. Jeg redigerte kort direkte i Supabase-dashboardet i månedsvis. Et enkelt skjema hadde spart meg for timer.
  3. Ikke ventet med PWA-oppsett til slutten. next-pwa er forholdsvis greit, men service workers har egne regler for caching som krasjer med Next.js ISR hvis du ikke tenker over det. Enklere å sette det opp fra dag én.

Hvorfor det var verdt det

Klink har ikke tusenvis av brukere. Det er ikke et forretningsbygg. Men det var den første appen jeg leverte fra idé til produksjon helt alene , uten team, uten kunder, uten noen å skylde på hvis noe ikke var bra nok.

Det lærte meg at jeg kan levere et helt produkt selv. Det er en annen type selvtillit enn å levere godt innenfor et team. Begge er viktige, men den første får du bare ved å gjøre noe ferdig helt alene.

Appen finner du på klinkn.no. Koden ligger på GitHub. Koden inneholder en overraskelse eller to.

Hvis du likte dette

Abonnér via RSS eller JSON Feed, eller ta kontakt.

Del på: LinkedIn · Redi AS · Spotify · Instagram · Wikidata