Van leeg scherm naar de eerste klant live — in acht dagen.
Fit in de Buurt is een leadplatform voor lokale sportstudio’s: landingspagina’s die converteren, een portaal om gebelde leads op te volgen, een gids op postcode en de facturatie eromheen. Ontwerp, bouw, database, hosting en beheer — solo gebouwd.
Live op fitindebuurt.nl · Row-level security op alles wat privé is · Next.js 16, React 19, Supabase
Het probleem
Een kleine sportstudio adverteert lokaal, stuurt mensen naar haar Instagram of naar een algemene website, en verliest ze daar. Wie wél reageert komt binnen via een DM, een mailtje of een appje; de eigenaar houdt in haar hoofd bij wie ze nog moet terugbellen. Twee dagen later is de helft vergeten en is de interesse weg.
De drie dingen die daarvoor bestaan lossen elk maar een stukje op. Een website-bouwer levert een pagina zonder opvolging. Een CRM is te zwaar voor iemand met een handvol leads per maand. Een linktree geeft geen enkel inzicht. En alles wat wél werkt, zet de persoonsgegevens van haar klanten op een plek waar zij geen zeggenschap over heeft.
Wat het geworden is
Eén platform dat drie soorten bezoekers tegelijk bedient — de sporter, de studio en de beheerder.
Voor de sporter
De homepage is een gids: vul je postcode in en zie welke studio’s, sportscholen, salons en praktijken het dichtst bij je zitten, met afstand, ervaringen en een knop naar het boekingssysteem of de leadpagina van die studio. Zonder cookies, zonder account, zonder dat we opslaan waar iemand naar zoekt.
Voor de studio
Een eigen leadpagina op naam.fitindebuurt.nl én fitindebuurt.nl/naam, gebouwd uit blokken en afgestemd op haar huisstijl. Wie zijn nummer achterlaat staat meteen in haar portaal, met een mail erbij. In dat portaal staat één lijst: wie moet ik bellen. Per lead houdt het systeem belpogingen bij, kun je met een notitie snoozen, en markeer je hem als gewonnen of weggedrukt.
Voor de beheerder
Een editor waarin ik een klantpagina opbouw en publiceer, statistiek per versie van die pagina, en facturatie met abonnementen, nummering, PDF’s en een nachtelijke cronjob.
Er staat nergens een belknop of een tel:-link. Dat is de hele opzet: een bezoeker laat zijn gegevens achter en de studio belt terug. Zo weet de eigenaar wie er geïnteresseerd was, ook als het gesprek niet meteen lukt.
Vier keuzes die het project bepalen
Beslissingen die door de hele codebase heen doorwerken — en het verschil maken tussen af en afgeraffeld.
Scheiding tussen klanten zit in de database, niet in de interface
Elke pagina heeft één eigenaar. Row-level security in Postgres bepaalt wie welke rij ziet; de app kan het niet omzeilen, ik ook niet. Er draait een testsuite die met echte inloggegevens van twee eigenaren probeert om elkaars leads te lezen, te wijzigen en te verwijderen. Zonder die groene test gaat er niets live.
De inhoud van een pagina staat op twee plekken, met opzet
Het sjabloon is een getypt bestand dat afsluit met satisfies LandingPagina — mist er een tekst, dan faalt de build. Wat ik in de editor aanpas komt als overlay in de database en valt bij het renderen over het sjabloon heen. Zo houd ik de garanties van de compiler én kan ik teksten wijzigen zonder te deployen.
Publiceren gaat onder een naam, cijfers horen bij een versie
Elke publicatie is een onveranderlijke rij in pagina_versies. De dagtellers onthouden per versie wat er geteld is, dus ik kan zien wat "Kortere hero" opleverde tegenover "Zomeractie", en een oude versie met één klik terugzetten als concept.
Alles in het Nederlands, tot in de database
Kolommen heten belpogingen, snooze_tot, weggedrukt_op. Dat leest voor de klant en voor mij hetzelfde, en het houdt de afstand klein tussen wat we in een gesprek zeggen en wat er in de code staat.
Drie onderdelen om uit te lichten
De plekken waar de meeste denk- en bouwtijd in ging.
De pagina-editor
Links een voorvertoning die meetypt, rechts de pagina van boven naar beneden: per blok één paneel met álles wat erbij hoort. Teksten pas je ook in de voorvertoning zelf aan: aanwijzen, klikken, typen — een bezoeker krijgt exact dezelfde HTML. De accentkleur staat in één CSS-variabele; met color-mix volgen daar vijf tinten uit, zodat elk blok bij elke huisstijl klopt.
De gids op postcode
Postcodes gaan naar de Locatieserver van het Kadaster (PDOK): open, gratis, geen sleutel. Wat terugkomt bewaren we in een eigen tabel, dus een tweede bezoeker met dezelfde postcode kost geen verzoek meer. Postgres rekent de afstand uit en geeft alleen studio’s terug die aan alle voorwaarden voldoen. Van de bezoeker bewaren we niets: geen cookie, geen IP.
Facturatie
Abonnementen per klant, automatische concepten uit een nachtelijke run, een factuurnummerreeks die niet mag springen, bedragen in centen, btw per regel en een PDF in de opslag. Een verstuurde factuur is bevroren: databasetriggers weigeren elke wijziging, omdat een verzonden factuur zeven jaar hetzelfde hoort te blijven.
Zoiets nodig voor jouw bedrijf?
Hoe het in elkaar zit
Eén Next.js-project bedient vier soorten bezoekers, gescheiden met route-groepen: de gids, de marketingpagina’s, de klantlandingspagina’s en de ingelogde kant. Subdomeinen lopen via proxy.ts, ververst de Supabase-sessie en sluit alles achter de inlog af.
| Laag | Keuze |
|---|---|
| App | Next.js 16 (App Router), React 19, TypeScript strict |
| Styling | Tailwind CSS 4, eigen componenten, volledig Nederlandse interface |
| Data + auth | Supabase (Postgres, auth, storage), regio Frankfurt |
| Validatie | Zod, aan beide kanten en op de omgevingsvariabelen |
| Resend, vijf transactionele mails met één opmaak | |
| @react-pdf/renderer, facturen in Supabase Storage | |
| Hosting | Vercel, wildcard *.fitindebuurt.nl, cronjobs, previews per push |
| Secrets | Doppler; geen sleutel in de repo |
| Monitoring | Sentry, alleen in productie |
- Twee nachtelijke taken op Vercel achter een gedeeld geheim: 06:00 facturatie, 04:00 opruimen van verlopen aanvragen.
- Geen namen, telefoonnummers of e-mailadressen in logs of foutmeldingen, geen IP-adressen in de statistiek.
- Leads uitsluitend server-side wegschrijven — de browser heeft geen schrijfrecht op de database.
- Een gesnoozede lead komt vanzelf terug: snooze is geen status maar een datum, geen achtergrondtaak die stil kan staan.
Kwaliteit en werkwijze
Eén opdracht is de poort: npm run check draait typecheck, ESLint zonder waarschuwingen, Prettier, alle unittesten en een volledige build. Wat daar niet doorheen komt, wordt niet als af gemeld. Husky en lint-staged doen hetzelfde bij elke commit, GitHub Actions herhaalt het op elke push.
| Wat | Omvang |
|---|---|
| Code | 265 bestanden, ± 33.000 regels TypeScript |
| Database | 27 migraties, 20 tabellen, RLS op alles wat privé is |
| Unittesten | 412, in 37 bestanden |
| Browsertesten (Playwright) | 52, in 9 bestanden |
| Sjablonen en blokken | 8 sjablonen, 12 herbruikbare blokken |
| Commits | 94, in acht dagen |
Browsertesten doen wat een mens doet
Inloggen, een lead binnenhalen, hem snoozen, een blok uitzetten in de editor, publiceren en controleren dat de openbare pagina meeverandert. Precies daar zitten de fouten die typecheck en unittesten niet zien.
Vindbaarheid ligt op vier plekken vast
robots.ts (met drieëntwintig AI-crawlers bij naam), een sitemap die de klantpagina’s uit de database haalt, een gegenereerde /llms.txt met de hele site in markdown, en gestructureerde gegevens per paginasoort — nooit meer dan op de pagina zelf staat.
Gebouwd met AI-assistentie, maar niet op de gok
Elk onderdeel is eerst uitgeschreven als plan in de repo — waarom, welke fasen, welke afwegingen — en daarna gebouwd. Vaste afspraken staan in een regelbestand dat elke sessie meeleest. De tests, de types en de row-level security zijn de rem.
Waar het nu staat
De site draait, de eerste echte klant staat live en in de gids, en de facturatie staat klaar voor haar eerste abonnement.
- Kaartweergave en eigen overzichtspagina’s per soort en plaats
- Geverifieerde Google-reviews
- Exporteren van leads
- Tweestapsverificatie
- A/B-testen over twee gepubliceerde versies tegelijk
Van idee naar live platform
Maatwerk dat blijft staan.
Een leadplatform, een portaal, een webshop of een interne tool — gebouwd met tests, types en privacy als fundament. Laten we bespreken wat je nodig hebt.







