{"version":"https://jsonfeed.org/version/1.1","title":"Marcus Jenshaug","home_page_url":"https://marcusjenshaug.no","feed_url":"https://marcusjenshaug.no/feed.json","description":"Fullstack-utvikler i Redi AS. Jeg bygger webapplikasjoner med Next.js, TypeScript og Supabase, og deler det jeg lærer underveis.","language":"nb-NO","authors":[{"name":"Marcus Jenshaug","url":"https://marcusjenshaug.no"}],"items":[{"id":"https://marcusjenshaug.no/blogg/soeknadsbasen-verktoeyet-jeg-ikke-fant-sa-bygget-selv","url":"https://marcusjenshaug.no/blogg/soeknadsbasen-verktoeyet-jeg-ikke-fant-sa-bygget-selv","title":"Søknadsbasen: verktøyet jeg ikke fant, så bygget selv","summary":"Et CV-verktøy og jobbsøker-tracker som startet som frustrasjon over eksisterende løsninger, og ble til et lite produkt jeg faktisk bruker selv.","content_text":"Søknadsbasen startet med irritasjon.\r\n\r\nJeg har søkt jobb et par ganger, og hver gang har jeg endt opp med samme rot: en mappe med halvferdige CV-filer, et regneark med søknadsstatus som er utdatert etter tre dager, og en liste med kontakter jeg ikke klarer å holde orden på. Det finnes mange verktøy for dette, men de fleste er enten for enkle til å være nyttige, eller så fulle av features at de krever en onboarding-prosess bare for å sende én søknad.\r\n\r\nJeg ville ha noe midt imellom. Noe jeg faktisk ville åpne.\r\n\r\nSå jeg bygget det selv.\r\n\r\n## Problemet med eksisterende verktøy\r\n\r\nDe fleste jobbsøker-verktøy tenker på jobbsøking som en liste. Du legger inn en stilling, setter en status, kanskje noterer litt. Det er greit for de første ukene. Etter en måned med aktiv søking har du femti rader i den listen, og du har mistet konteksten på de fleste.\r\n\r\nDet jeg manglet var en måte å tenke på jobbsøking som noe mer strukturert, en pågående prosess med faser, ikke bare et register over hva du har sendt inn.\r\n\r\nDet jeg endte opp med å kalle \"jobbsøk-sesjoner\" var egentlig dette: at en jobbsøk-periode er sitt eget objekt i systemet. Den har en start, den har et mål, den har tilhørende søknader, og den har et utfall. Det lar deg se på én søknad i konteksten av hele perioden den tilhørte, ikke løsrevet fra alt annet.\r\n\r\nDet høres kanskje komplisert ut, men det er intuitivt når du bruker det. Det er bare riktig abstraksjonsnivå for problemet.\r\n\r\n## Å bygge for deg selv er en annen øvelse\r\n\r\nJeg er vant til å bygge for klienter, eller for brukere jeg aldri møter. Det er en type disiplin: du må gjøre ting eksplisitt, du må skrive onboarding, du må hjelpe noen som ikke kan koden til å forstå hva knappen gjør.\r\n\r\nÅ bygge for meg selv åpnet for snarveier som var fristende og farlige på samme tid.\r\n\r\nDen positive varianten: jeg trengte ikke bygge alt. Jeg trengte ikke en admin-panel for å redigere data, jeg kunne bare gå i databasen. Jeg trengte ikke en fancy onboarding-flyt, jeg visste allerede hvordan ting funket. Det sparte meg for mye tid på ting som ikke mattered.\r\n\r\nDen negative varianten: jeg la for mye implisitt kunnskap inn i produktet. Funksjonalitet som gir mening for meg, men som ikke ville gitt mening for noen andre. Det er ikke et problem nå, men det er en begrensning hvis jeg noen gang vil la andre bruke det.\r\n\r\nLeksjonen: bygg for deg selv med klar bevissthet om hva du ofrer av bredde. Det er et bevisst valg, ikke en bug.\r\n\r\n## CV-byggeren er den vanskeligste delen\r\n\r\nEn jobbsøker-tracker er egentlig bare en database med et grensesnitt. Det er ikke trivielt, men det er logisk.\r\n\r\nEn CV-bygger er noe helt annet.\r\n\r\nCV-er har meninger om alt. Font-valg sender signaler om bransje og senioritet. Layout-hierarki påvirker hva rekrutterer ser i de første tre sekundene. Hva du inkluderer og hva du utelater er redaksjonelle valg, ikke bare innfylling av felter.\r\n\r\nJeg brukte mye mer tid på CV-delen enn jeg trodde. Ikke på teknisk implementasjon, men på å tenke gjennom hva et CV-verktøy faktisk skal hjelpe deg med. Er det å gjøre CV-en pyntelig? Er det å hjelpe deg velge hva du skal ta med? Er det å generere tekst?\r\n\r\nJeg landet på at det viktigste var kontroll og forutsigbarhet. Eksportert PDF skal se ut akkurat som forhåndsvisningen. Endringer skal reflekteres med én gang. Du skal aldri lure på om det du ser er det rekrutterer ser.\r\n\r\nDet er lavere ambisjon enn \"AI-generert CV tilpasset stillingen\", men det er noe jeg faktisk stoler på.\r\n\r\n## Tekniske valg jeg er fornøyd med\r\n\r\nNext.js og Supabase er defaultene mine nå, og de funket greit her også. Det er ikke spennende å skrive om, men det er poenget: default-valg som funker lar deg bruke energien på produktet, ikke på infrastruktur.\r\n\r\nTo valg jeg er spesielt fornøyd med:\r\n\r\nÅ modellere jobbsøk-sesjoner som første-klasses modell i skjemaet, ikke bare som et tag eller en status på søknaden. Det ga meg query-muligheter jeg ikke hadde tenkt over da jeg designet det, og det gjorde det naturlig å vise aggregert statistikk per sesjon.\r\n\r\nÅ la PDF-generering skje server-side. Jeg vurderte client-side PDF-generering lenge fordi det er enklere å sette opp. Men server-side gir konsistent rendering uavhengig av nettleser, og det er verdt kompleksiteten for noe som er kjernen i produktet.\r\n\r\n## Det jeg undervurderte\r\n\r\nFilhåndtering er alltid mer komplisert enn det ser ut. Jeg ville la brukere laste opp profilbilde til CV-en, og jeg estimerte en halvtime på oppsett. Det tok en dag og tre omskrivinger, og jeg er fortsatt ikke hundre prosent fornøyd med flyten.\r\n\r\nDet er ikke noe uvanlig i seg selv. Det som overrasket meg var at jeg undervurderte det selv etter å ha gjort det før. Noe med filhåndtering lar seg ikke fullt lære bort, det er noe du må møte på nytt i konteksten av akkurat dette prosjektet.\r\n\r\nJeg undervurderte også hvor mye tid jeg ville bruke på å designe selve CV-malen. Teknisk er det ikke vanskelig. Estetisk er det uendelig justerbart. Jeg satte meg en hard stopp-regel etter tredje iterasjon: dette er godt nok, ikke bra nok til å distrahere.\r\n\r\n## Hva jeg skulle gjort annerledes\r\n\r\nLaget mer av admin-panelet tidligere. Jeg redigerte direkte i Supabase i starten, akkurat som jeg lærte av Klink-prosjektet, og glemte leksjonen likevel.\r\n\r\nBestemt meg for PDF-bibliotek på dag én. Jeg byttet mellom tre alternativer og mistet tid på hver overgang. Neste gang setter jeg meg ned og evaluerer ordentlig en gang, ikke itererer gjennom dem i produksjon.\r\n\r\nVært tydeligere på hva produktet ikke er. Uten den grensen er scope åpen i begge ender, og det er dyrt.\r\n\r\n## Hvorfor det var verdt det\r\n\r\nSøknadsbasen er ikke et stort produkt. Det er et lite verktøy som hjelper én person, meg, å holde orden på noe som ellers skaper friksjon.\r\n\r\nMen det var den første gangen jeg bygget noe jeg faktisk brukte aktivt mens jeg bygget det. Det gir en annen type feedback enn å se på analytics eller lese brukerrapporter. Du merker på kroppen når noe ikke funker bra nok, fordi du er frustrert av det selv.\r\n\r\nDet er en luksus de fleste produktutviklere ikke har, å være sin egen mest kravstore bruker. Jeg prøver å ta vare på den leksjonen i andre prosjekter også: forstå brukerens frustrasjon så godt at du kjenner den selv.\r\n\r\nSøknadsbasen lever på søknadsbasen.no. Den er ikke klar for allmenheten ennå, men det er dit det går.","date_published":"2026-04-24T09:39:52.799+00:00","date_modified":"2026-04-24T09:39:52.799+00:00","tags":["solodev","nextjs","supabase","produktutvikling","jobbsøking"],"image":"https://marcusjenshaug.nohttps://dzfrsfecffomzwlhemzt.supabase.co/storage/v1/object/public/media/blog/262de503-66ca-49a7-ae0a-7e8f21cd5b96.png"},{"id":"https://marcusjenshaug.no/blogg/to-og-et-halvt-ar-som-eneste-utvikler","url":"https://marcusjenshaug.no/blogg/to-og-et-halvt-ar-som-eneste-utvikler","title":"To og et halvt år som eneste utvikler","summary":"En retrospektiv etter å ha bygget Eiendomsavtaler.no fra første linje kode. Fra innleid konsulent til grunnleggers-utvikler, gjennom tre tekniske generasjoner.","content_text":"Jeg kom inn i Eiendomsavtaler.no høsten 2023 , først som innleid konsulent gjennom mitt eget byrå, [Spiderweb AS](/prosjekter/spiderweb). Det fantes ingen teknisk plattform ennå. Jobben var å bygge den. I januar 2025 ble jeg fast ansatt, og samme år la jeg ned Spiderweb for å fokusere fullt på Eiendomsavtaler. Jeg var eneste utvikler. Alt fra arkitektur til produksjon, fra sikkerhet til overvåkning, lå hos meg.\r\n\r\nI mai 2026 gir jeg det fra meg. Plattformen er i stabil drift, har vært det lenge, og det er ingen dramatisk grunn til at jeg slutter , jeg skal videre til Redi AS og bygge andre ting. Men overgangen er en god anledning til å skrive ned det jeg faktisk har lært. Ikke på LinkedIn-format med \"drev vekst og optimaliserte ytelse\", men det som sitter i kroppen etter to og et halvt år.\r\n\r\n## Tre generasjoner på to og et halvt år\r\n\r\nDet første du må vite er at jeg bygget plattformen gjennom tre tekniske generasjoner.\r\n\r\n**Generasjon 1** var en WordPress-installasjon. Advanced Custom Fields for strukturerte skjemaer, WP User Frontend for opplasting, WP Statistics for sporing , plugin-stacken man ender opp med når man trenger å komme fort i gang og validere at produktet har en rolle i markedet. Jeg valgte det bevisst. Et MVP skal leveres, ikke være arkitektonisk imponerende.\r\n\r\n**Generasjon 2** var min første Next.js-app , en investor-plattform, et sideprosjekt innenfor samme organisasjon. Jeg skrev den mens jeg fortsatt driftet WordPress-siden. Den var i praksis min læringsarena: første gangen jeg jobbet med App Router, server components, server actions. Den lever fortsatt, men ble aldri produkt-hovedkanalen.\r\n\r\n**Generasjon 5** (ja, vi hoppet over 3 og 4 , mer om det under) er den som kjører i dag. Full rewrite til TypeScript, Next.js, PostgreSQL. Ingen WordPress igjen. Dette er det jeg overleverer.\r\n\r\n## Hvorfor versjonstellingen hopper\r\n\r\nVersjonene 3 og 4 er ikke rewrites , de er arkitekturutkast som aldri ble deployet. Jeg prøvde først en tyngre tilnærming med et separat backend-API og Next.js som ren frontend. Det var overdimensjonert for et team på én. Så prøvde jeg en arkitektur med mer spesialiserte moduler. Også overdimensjonert.\r\n\r\nDet jeg landet på i Versjon 5 var kjedelig: én Next.js-app med server actions, PostgreSQL som primærdatabase, bildebehandling via sharp, type-validering med zod. Ingen microservices, ingen separate API-er, ingen eventing bus jeg ville brukt tre måneder på å sette opp riktig.\r\n\r\nDen viktigste leksjonen fra de forkastede versjonene var at **kompleksitet straffer seg eksponentielt når du er alene**. Hver abstraksjon du legger til er en ting bare du kjenner, bare du kan feilsøke, bare du kan endre. Og om to år er ikke engang du sikker på at du husker hvorfor.\r\n\r\n## Hvorfor jeg tok det\r\n\r\nEneste-utvikler-jobber er polariserende i bransjen. Halvparten advarer deg: ingen pair programming, ingen kode-gjennomgang, ingen å sparre med, fullt ansvar når ting feiler klokka tre på natta. Den andre halvparten snakker om eierskap, fart og fravær av komitéer.\r\n\r\nBegge sider har rett. Ingen av sidene forbereder deg på hva det faktisk er å gjøre det.\r\n\r\nGrunnen til at jeg sa ja var ikke romantikken rundt \"bygge alt selv\". Det var at jeg ville vite om jeg *kunne*. Jeg hadde jobbet i team i flere år, og jeg hadde alltid hatt en følelse av at når noe gikk bra, var det fordi teamet var godt. Jeg visste ikke hva som var meg og hva som var dem. Å være eneste utvikler er den eneste måten å finne ut av det på.\r\n\r\n## Det jeg undervurderte\r\n\r\nDe første månedene gikk på tekniske avgjørelser. Rammeverk, hosting, database, auth, overvåkning. Jeg hadde en arkitektur jeg var fornøyd med på papiret, og jeg fikk den til å kjøre.\r\n\r\nDet jeg ikke hadde planlagt for var *alt det andre*. En eneste-utvikler er ikke bare utvikleren. Du er også:\r\n\r\n- Sikkerhetsansvarlig som må følge med på CVE-er\r\n- DevOps-ingeniøren som eier deploy-pipelinen\r\n- Databaseadministratoren som må svare \"hvorfor er det tregt nå?\"\r\n- Supporten når noe ikke funker for en kunde klokka 16:30 fredag\r\n- Tech-lead-en som må si nei til features som er kule men unødvendige\r\n- Product-en som oversetter \"kan vi ikke ha en knapp som...\" til noe faktisk utviklbart\r\n\r\nDet siste er det jeg ble dårligst forberedt på. Kode er overraskende lite av jobben når du er alene. Mye mer av dagen går til kommunikasjon, prioritering og å forklare hvorfor ting tar den tiden de tar.\r\n\r\n## WordPress hadde noen av svarene\r\n\r\nNoe av det jeg synes var mest overraskende underveis var hvor mye WordPress faktisk hadde rett om. Jeg hadde utviklet i WordPress før, og jeg visste at stacken har dårlig rykte blant mer \"moderne\" utviklere. Men det løser reelle problemer veldig effektivt:\r\n\r\n- **Strukturerte skjemaer uten å skrive dem** (ACF)\r\n- **Brukergenerert innhold med moderering** (WP User Frontend)\r\n- **Analyse uten tredjeparts-trackere** (WP Statistics)\r\n\r\nDa jeg bygget v5, måtte jeg re-implementere hver av disse. Og det er *mye* kode for noe som før fyltes ut på femten minutter i et admin-panel. Det jeg vant var typesikkerhet, bedre performance, og kontroll over hele stacken. Det jeg tapte var utviklings-hastighet på ting som WordPress bare hadde løst.\r\n\r\nKonklusjonen min etter migreringen er ikke \"WordPress er dårlig\" eller \"egen-bygget er bedre\". Det er at **valg av rammeverk er en kalibreringsøvelse, ikke et prinsipp**. WordPress var riktig valg for den fasen plattformen var i. Det er ikke riktig for denne fasen. Neste fase får vise om v5 fortsatt er det riktige valget.\r\n\r\n## Å være sin egen kode-reviewer\r\n\r\nDen tyngste enkelt-øvelsen i å være alene er å vurdere sitt eget arbeid nøkternt. Det er kjent, det er skrevet mye om, og likevel var det vanskeligere enn jeg trodde.\r\n\r\nNår du er i et team, får du motstand. Noen spør \"hvorfor gjorde du det sånn?\", og du må forsvare valget , og noen ganger innser du midt i forsvaret at du ikke har et godt svar. Uten den motparten forsvinner forsvaret også. Du skriver kode, du merger den, du glemmer hvorfor.\r\n\r\nDet jeg landet på var to enkle ting:\r\n\r\n**Å skrive små commits med tydelige meldinger.** Ikke fordi noen skulle lese dem senere , ingen skulle , men fordi jeg som skrev dem, tvang meg selv til å formulere *hva* jeg hadde gjort og *hvorfor*. Jeg kom i det minste halvveis til en begrunnelse.\r\n\r\n**Å la det ligge over natten før merge når det var stort.** Kveldsgløden etter at noe endelig funker er den verste reviewer. Morgen-meg er en strengere kollega enn kveld-meg.\r\n\r\nIngen av disse erstatter en ekte pair. Men de to sammen gjorde at jeg tok færre dumme snarveier enn jeg ellers ville gjort.\r\n\r\n## Om å bygge for én klient du aldri møter\r\n\r\nEiendomsavtaler.no har reelle brukere. Megler-profesjonelle, eiendomsaktører, folk som bruker plattformen for å lande faktiske avtaler. Jeg har aldri møtt noen av dem ansikt til ansikt. Det jeg visste om dem, visste jeg gjennom forretningssiden , folk som oversatte behov til meg og tok mine tekniske svar tilbake til dem.\r\n\r\nDet formet hvordan jeg jobbet. Jeg måtte bli flinkere til å stille spørsmål som ikke forutsatte at jeg hadde sett problemet selv. \"Hva prøver brukeren å gjøre når dette feiler?\" i stedet for \"hva er feilen?\". Det første gir deg kontekst. Det andre gir deg en stack trace.\r\n\r\nNår du jobber alene uten direkte brukerkontakt, er forretningssiden din eneste bro til virkeligheten. Jeg lærte å respektere den rollen mye mer enn jeg gjorde før.\r\n\r\n## Performance-arbeidet jeg lærte mest av\r\n\r\nEn av de mest lærerike øvelsene i v5 var ytelses-arbeidet. Jeg førte en enkel audit-fil i repoet der jeg noterte hvilke sider som trakk ned, hva som forårsaket det, og hva jeg gjorde for å fikse det. Ingenting avansert , bare en markdown-fil som vokste over tid.\r\n\r\nDet jeg lærte av den øvelsen var at de fleste performance-problemer er kjedelige. Det er sjelden at løsningen er \"bytt til en raskere runtime\" eller \"implementer en cache-strategi\". Oftere er det at et bilde ikke er lazy-loaded, en database-spørring henter mer enn nødvendig, eller en komponent rendres klient-side fordi utvikleren (altså jeg) glemte å sjekke om den kunne kjøre server-side.\r\n\r\nKjedelig fix, stor gevinst. Den typen arbeid belønnes ikke av det tekniske miljøet , ingen holder foredrag om \"jeg la til `sizes`-attributten på `<Image>`\" , men det er det som faktisk utgjør forskjellen i Core Web Vitals.\r\n\r\n## Hvorfor enkle løsninger vant\r\n\r\nJeg begynte prosjektet med en arkitektonisk ambisjon som var litt for stor for problemet. Microservices her, event-drevet der, queue for de tunge jobbene, Kubernetes en gang i fremtiden. Ingen av det var teknisk feil , det var bare feil *størrelse*.\r\n\r\nDet jeg endte med var mye nærmere kjedelig. En monolitt med klare grenser mellom moduler. En primærdatabase med replika. En cache-lag på de åpenbare stedene. CI/CD som er rett-frem nok til at jeg kan gjøre endringer i den uten å være redd for å brekke den. Overvåkning som gir meg to-tre metrics jeg faktisk følger med på, ikke tjue jeg ignorerer.\r\n\r\nGrunnen til at dette funket er ikke at jeg hadde rett i mine valg. Det er at jeg var eneste utvikler, og kompleksitet straffer seg eksponentielt når du er alene.\r\n\r\nHvis jeg bygget den samme plattformen i dag, ville jeg begynt enda enklere.\r\n\r\n## Det som var verdt det\r\n\r\nDet jeg tar med meg videre er ikke at jeg klarte det , det var aldri spørsmålet. Det jeg tar med meg er at jeg nå har en kalibrering på hva som faktisk koster tid når du eier hele stacken. Jeg vet hva \"vi må bare få det ut\" betyr for en real-world deploy, ikke bare for en sprint. Jeg vet hva det kveles av når en arkitektur er for stor for teamet som skal drifte den. Jeg vet hva du vinner ved å bytte fra et ferdig rammeverk, og hva du taper.\r\n\r\nDen kalibreringen er vanskelig å få på annen måte. Det koster deg to og et halvt år, en del helger og noen kvelder du skulle ønske du kunne hatt tilbake. Men den sitter når du får den.\r\n\r\n## Hva som skjer nå\r\n\r\nPlattformen lever videre uten meg , overleveringen har vært ryddig, dokumentasjonen er der, og det nye teamet har fått tilgang til alt som trengs. Jeg går videre til [Redi AS](https://redi.as) og fullstack-utvikling i et multi-tenant produkt, der jeg er én utvikler blant flere. Det blir en annen jobb på måter jeg nok fortsatt undervurderer.\r\n\r\nMen jeg tror det blir enklere å være en del av et team når man vet hva man faktisk bidrar med , og hva man trenger fra de andre.\r\n\r\nDu finner plattformen på [eiendomsavtaler.no](https://eiendomsavtaler.no).","date_published":"2026-04-20T16:50:00+00:00","date_modified":"2026-04-22T14:51:18.964+00:00","tags":["arkitektur","solo-utvikling","retrospektiv","plattform","wordpress","nextjs"],"image":"https://marcusjenshaug.nohttps://dzfrsfecffomzwlhemzt.supabase.co/storage/v1/object/public/media/blog/047683c6-4f47-43d0-bc4b-46989a64751c.png"},{"id":"https://marcusjenshaug.no/blogg/hva-jeg-laerte-av-a-bygge-klink-alene","url":"https://marcusjenshaug.no/blogg/hva-jeg-laerte-av-a-bygge-klink-alene","title":"Hva jeg lærte av å bygge Klink alene","summary":"Et drikkespill-webapp som skulle være en helgeprosjekt, og ble til en skole i scope, arkitektur og det å levere noe ferdig.","content_text":"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.\r\n\r\nHer 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.\r\n\r\n## Scope er den viktigste avgjørelsen\r\n\r\nDen 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.\r\n\r\nDet jeg gjorde riktig var å låse meg tidlig til noen bevisste begrensninger:\r\n\r\n- **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.\r\n- **Ingen multiplayer.** Én enhet, delt mellom spillerne , akkurat som et fysisk kortspill.\r\n- **Ett språk.** Norsk. Klink er et norsk konsept for en norsk kontekst. i18n er et ork jeg ikke trenger nå.\r\n\r\nHver 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.\r\n\r\n## Server-first er stort sett riktig, men ikke alltid\r\n\r\nJeg 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.\r\n\r\nKlink 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:\r\n\r\n1. Spillet skal føles umiddelbart. Ingen latency mellom \"trykk neste\" og \"nytt kort vises\".\r\n2. Det skal funke offline. PWA-støtte betyr at appen må kunne kjøre uten nettverk.\r\n3. Det er ikke sensitivt. Hvem som får hvilket kort kan lekke til klienten , det er klienten.\r\n\r\nDet 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.\r\n\r\n## Fisher-Yates, og hvorfor Math.random er nok\r\n\r\nEt 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.\r\n\r\nJeg byttet til [Fisher-Yates](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle):\r\n\r\n```typescript\r\nfunction shuffle(array) {\r\n  const result = [...array];\r\n  for (let i = result.length - 1; i > 0; i--) {\r\n    const j = Math.floor(Math.random() * (i + 1));\r\n    [result[i], result[j]] = [result[j], result[i]];\r\n  }\r\n  return result;\r\n}\r\n```\r\n\r\nJeg 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.\r\n\r\nEt prinsipp jeg prøver å huske: løsningen skal være så robust som problemet krever, ikke som problemet kunne tenkes å kreve.\r\n\r\n## Designet er forskjellen\r\n\r\nKlink 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:\r\n\r\n- Lime og mørkegrønn som dominerende farger (Børst er blå/oransje)\r\n- Sans-serif med karakter, ikke generisk\r\n- Glass-morphism med hvite container-kort på fargede bakgrunner\r\n- 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.\r\n\r\nDet 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.\r\n\r\n## Deploy-flyten som faktisk holder\r\n\r\nPå Eiendomsavtaler.no bygget jeg en CI/CD-pipeline med steg, tester, staging, godkjenninger, alt. Det var riktig der.\r\n\r\nPå 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\".\r\n\r\nDette 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.\r\n\r\n## Hva jeg skulle gjort annerledes\r\n\r\n1. **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.\r\n2. **Laget et admin-panel tidligere.** Jeg redigerte kort direkte i Supabase-dashboardet i månedsvis. Et enkelt skjema hadde spart meg for timer.\r\n3. **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.\r\n\r\n## Hvorfor det var verdt det\r\n\r\nKlink 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.\r\n\r\nDet 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.\r\n\r\nAppen finner du på [klinkn.no](https://klinkn.no). Koden ligger [på GitHub](https://github.com/MarcusJenshaug1/klink). Koden inneholder en overraskelse eller to.","date_published":"2026-04-02T04:23:00+00:00","date_modified":"2026-04-22T14:30:16.224+00:00","tags":["nextjs","supabase","pwa","sideprosjekt"],"image":"https://marcusjenshaug.nohttps://dzfrsfecffomzwlhemzt.supabase.co/storage/v1/object/public/media/blog/8637523f-9af6-44eb-bc9f-09d646d966b1.png"}]}