SnoekByte LogoSnoekByte
Fallstudie · Live i produktion
Leadplattform för lokala träningsstudior

Från tom skärm till första kunden live — på åtta dagar.

Fit in de Buurt är en leadplattform för lokala träningsstudior: landningssidor som konverterar, en portal för att följa upp uppringda leads, en guide efter postnummer och faktureringen runt omkring. Design, bygge, databas, hosting och drift — byggt solo.

Se plattformen live
Roll
Design, bygge, databas, hosting & drift (solo)
Period
Augusti 2026 · 8 dagar till första kunden live
Stack
Next.js 16 · React 19 · TypeScript · Tailwind 4 · Supabase · Resend · Vercel
Live
fitindebuurt.nl

Live på fitindebuurt.nl · Row-level security på allt privat · Next.js 16, React 19, Supabase

8
dagar till första kunden live
±33k
rader TypeScript (265 filer)
464
automatiserade tester
Live
första riktiga kunden i produktion

Problemet

En liten träningsstudio annonserar lokalt, skickar folk till sitt Instagram eller en generell webbplats, och tappar dem där. Den som svarar kommer in via ett DM, ett mejl eller ett sms; ägaren håller i huvudet reda på vem hon ska ringa tillbaka. Två dagar senare är hälften glömd och intresset borta.

De tre saker som finns för detta löser var och en bara en bit. En webbplatsbyggare levererar en sida utan uppföljning. Ett CRM är för tungt för någon med en handfull leads i månaden. En linktree ger ingen insikt alls. Och allt som faktiskt fungerar lägger hennes kunders personuppgifter någonstans hon inte har kontroll över.

Vad det blev

En plattform som betjänar tre sorters besökare samtidigt — idrottaren, studion och operatören.

För idrottaren

Startsidan är en guide: ange ditt postnummer och se vilka studior, gym, salonger och mottagningar som ligger närmast, med avstånd, omdömen och en knapp till studions bokningssystem eller leadsida. Utan cookies, utan konto och utan att vi sparar vad någon söker efter.

För studion

En egen leadsida på namn.fitindebuurt.nl och fitindebuurt.nl/namn, byggd av block och anpassad till profilen. Den som lämnar sitt nummer hamnar direkt i portalen, med ett mejl bredvid. Portalen har en lista: vem ska jag ringa. Per lead spårar systemet ringförsök, låter dig snooza med en notering och markera som vunnen eller bortvald.

För operatören

En editor där jag bygger och publicerar en kundsida, statistik per version av den sidan, och fakturering med prenumerationer, numrering, PDF:er och ett nattligt cronjobb.

Det finns ingenstans en ringknapp eller en tel:-länk. Det är hela idén: en besökare lämnar sina uppgifter och studion ringer tillbaka. Så vet ägaren vem som var intresserad, även när samtalet inte går fram direkt.

Fyra val som formar projektet

Beslut som går genom hela kodbasen — och gör skillnad mellan klart och slarvigt.

1

Åtskillnaden mellan kunder sitter i databasen, inte i gränssnittet

Varje sida har en ägare. Row-level security i Postgres avgör vem som ser vilken rad; appen kan inte kringgå det, inte jag heller. En testsvit använder två ägares riktiga inloggningar för att försöka läsa, ändra och radera varandras leads. Utan det gröna testet går inget live.

2

En sidas innehåll bor på två ställen, med avsikt

Mallen är en typad fil som slutar med satisfies LandingPagina — saknas en text faller bygget. Det jag ändrar i editorn blir ett overlay i databasen och läggs över mallen vid rendering. Så behåller jag kompilatorns garantier och kan ändra text utan att deploya.

3

Man publicerar under ett namn, siffror hör till en version

Varje publicering är en oföränderlig rad i pagina_versies. Dagsräknarna minns vad som räknats per version, så jag ser vad "Kortare hero" gav mot "Sommarkampanj", och återställer en gammal version som utkast med ett klick.

4

Allt på nederländska, ända in i databasen

Kolumnerna heter belpogingen, snooze_tot, weggedrukt_op. Det läses lika för kunden och för mig, och håller avståndet litet mellan vad vi säger i ett samtal och vad som står i koden.

Tre delar värda att lyfta fram

Ställena där mest tanke- och byggtid gick åt.

Sideditorn

Till vänster en live-förhandsvisning, till höger sidan uppifrån och ned: en panel per block med allt som hör dit. Text redigerar du i själva förhandsvisningen — peka, klicka, skriv — och en besökare får exakt samma HTML. Accentfärgen bor i en CSS-variabel; color-mix härleder fem nyanser, så varje block passar varje profil.

Guiden efter postnummer

Postnummer går till Kadasters lokaliseringsserver (PDOK): öppen, gratis, ingen nyckel. Det som kommer tillbaka sparas i en egen tabell, en andra besökare med samma postnummer kostar ingen förfrågan. Postgres räknar ut avståndet och returnerar bara studior som uppfyller alla villkor. Av besökaren behåller vi inget: ingen cookie, ingen IP.

Fakturering

Prenumerationer per kund, automatiska utkast från en nattlig körning, en fakturanummerserie som inte får hoppa, belopp i ören, moms per rad och en PDF i lagringen. En skickad faktura är fryst: databastriggers vägrar varje ändring, eftersom en skickad faktura ska vara likadan i sju år.

Behöver du något liknande för ditt företag?

Hur det hänger ihop

Ett Next.js-projekt betjänar fyra sorters besökare, åtskilda med route-grupper: guiden, marknadssidorna, kundlandningssidorna och den inloggade sidan. Subdomäner går via proxy.ts, som förnyar Supabase-sessionen och låser allt bakom inloggningen.

LagerVal
AppNext.js 16 (App Router), React 19, TypeScript strict
StylingTailwind CSS 4, egna komponenter, helt nederländskt gränssnitt
Data + authSupabase (Postgres, auth, storage), region Frankfurt
ValideringZod, på båda sidor och på miljövariablerna
MailResend, fem transaktionsmejl med en layout
PDF@react-pdf/renderer, fakturor i Supabase Storage
HostingVercel, wildcard *.fitindebuurt.nl, cronjobb, preview per push
HemligheterDoppler; ingen nyckel i repot
ÖvervakningSentry, endast i produktion
  • Två nattliga jobb på Vercel bakom en delad hemlighet: 06:00 fakturering, 04:00 rensning av utgångna förfrågningar.
  • Inga namn, telefonnummer eller e-postadresser i loggar eller felmeddelanden, inga IP-adresser i statistiken.
  • Leads skrivs endast på serversidan — webbläsaren har ingen skrivrätt till databasen.
  • En snoozad lead kommer tillbaka av sig själv: snooze är inte en status utan ett datum, inget bakgrundsjobb som kan stanna.

Kvalitet och arbetssätt

Ett kommando är porten: npm run check kör typecheck, ESLint utan varningar, Prettier, alla enhetstester och ett fullt bygge. Det som inte tar sig igenom räknas inte som klart. Husky och lint-staged gör detsamma vid varje commit, GitHub Actions upprepar det vid varje push.

VadOmfattning
Kod265 filer, ± 33 000 rader TypeScript
Databas27 migrationer, 20 tabeller, RLS på allt privat
Enhetstester412, i 37 filer
Webbläsartester (Playwright)52, i 9 filer
Mallar och block8 mallar, 12 återanvändbara block
Commits94, på åtta dagar

Webbläsartester gör vad en människa gör

Logga in, fånga en lead, snooza den, stäng av ett block i editorn, publicera och kontrollera att den publika sidan också ändras. Precis där sitter buggarna som typecheck och enhetstester inte ser.

Hittbarhet är fastlagd på fyra ställen

robots.ts (med tjugotre AI-crawlers vid namn), en sitemap som hämtar kundsidor från databasen, en genererad /llms.txt med hela sajten i markdown, och strukturerad data per sidtyp — aldrig mer än vad som står på sidan själv.

Byggt med AI-assistans, men inte på måfå

Varje del skrevs först ut som en plan i repot — varför, vilka faser, vilka avvägningar — och byggdes sedan. Fasta överenskommelser bor i en regelfil som varje session läser. Testerna, typerna och row-level security är bromsen.

Var det står nu

Sajten är igång, den första riktiga kunden är live och i guiden, och faktureringen står redo för hennes första prenumeration.

På färdplanen
  • Kartvy och egna översiktssidor per typ och plats
  • Verifierade Google-omdömen
  • Export av leads
  • Tvåfaktorsautentisering
  • A/B-tester över två publicerade versioner samtidigt

Från idé till live-plattform

Skräddarsydd mjukvara som håller.

En leadplattform, en portal, en webbshop eller ett internt verktyg — byggt med tester, typer och integritet som grund. Låt oss prata om vad du behöver.

Kontakt