Alle artikelen

Multi-tenant SaaS: 7 valkuilen die je pas leert in productie

Zeven concrete fouten die ik heb gemaakt of langs heb zien komen bij het bouwen van Salonnare - multi-tenant SaaS is hard, en deze heb je liever nooit zelf meegemaakt.

3 min leestijd
saasmulti-tenantarchitecture

Een multi-tenant SaaS bouwen voelt als een standaard webapp met één extra kolom in de database. In de praktijk zit er tussen 'werkt voor één klant' en 'werkt voor 100 klanten zonder dat ze elkaars data zien' een afgrond waar de meeste junior teams in vallen.

Dit zijn zeven concrete valkuilen uit Salonnare's productie traject die ik iedereen aanraad vooraf te internaliseren.

1. Tenant ID vergeten in één query is game over

De klassieke: één SELECT * FROM bookings WHERE status = 'scheduled' zonder tenant_id filter en ineens zien alle salons elkaars afspraken. Dit is geen hypothetisch risico - dit is de top-1 fout in multi-tenant systemen.

Oplossing: een tenant-scoped query wrapper. In Salonnare's Drizzle implementatie wordt op elke select(), insert(), update() en delete() automatisch een tenant_id filter geïnjecteerd. Developers kunnen simpelweg niet per ongeluk cross-tenant data lezen - de wrapper weigert de query anders.

// Route handler
const rows = await req.db.select().from(bookings).where(eq(bookings.status, 'scheduled'));
// Wrapper injecteert: AND bookings.tenant_id = ?

Plus 19 unit tests die expliciet testen dat de wrapper niet omzeild kan worden. Dat is de hoeveelheid paranoia die je nodig hebt.

2. Audit log zonder tenant_id

Als je audit log tabel géén tenant_id heeft, kan een onderzoeker na een incident niet zien welke tenant welke actie deed. En als je hem wel hebt maar developers vergeten hem in te vullen - even erg. Zet tenant_id als NOT NULL en enforced via database constraint.

3. Background jobs zonder tenant context

Email workers, cron jobs en andere async taken draaien buiten de HTTP request context. Heel makkelijk om daar tenantId te vergeten mee te geven, waarna de job tegen de verkeerde dataset draait.

Oplossing: elke enqueue functie verplicht tenantId als parameter. De job payload bevat tenant_id, de worker hydrateert de tenant-scoped DB wrapper voordat hij de handler aanroept.

4. Subscription cancellation doet geen data cleanup

Klant zegt op, abonnement wordt cancelled - en de data blijft maanden later nog in productie. Dit is zowel een GDPR issue (bewaartermijn) als een cost issue (storage + backups).

Oplossing: een retention job die 30 dagen na cancellation de data soft-deleted, en 90 dagen na cancellation hard-deleted. Plus een export tool zodat klanten hun eigen data kunnen downloaden voordat de retention klok tikt.

5. Feature flags per plan zonder centralized config

Elke plan-limiet hardcoden in individuele routes eindigt in een bende. "Pro tier krijgt 50 gebruikers" is over zes routes verspreid, en na vier plan aanpassingen weet niemand meer wat waar staat.

Oplossing: één plan_limits.ts config met PLAN_LIMITS[plan].maxStaff, plus middleware requireFeature('loyalty') en checkStaffLimit() op de create-route. Plan wijzigingen vragen exact één commit.

6. Webhook delivery zonder idempotency

Stripe stuurt webhooks soms twee keer. Mollie ook. Als je betaling-handler geen idempotency check heeft, registreer je een €29 abonnement als €58 - en de klant eist terecht zijn geld terug.

Oplossing: bij elke webhook event sla je het event.id op in een processed_webhooks tabel. Voor handler-logica uitvoert checkt hij of het event al verwerkt is. Simpel, cruciaal, te vaak overgeslagen.

7. Subdomein SSL renewals vergeten in productie

Elk tenant een eigen subdomein? Check. Automatische SSL via Let's Encrypt? Meestal ja. Maar als je wildcard cert gebruikt, vergeten renewals breken alle tenants tegelijk. En Let's Encrypt's 90-dagen termijn komt sneller dan je denkt.

Oplossing: Certbot met DNS-01 challenge voor wildcard certs, cron job die elke dag checkt, en een monitoring alert als het cert binnen 14 dagen verloopt. Twee weken buffer geeft je tijd om handmatig in te grijpen.

Conclusie

Multi-tenant SaaS bouwen is eenmalige architectuur-investering voor een terugkerend product. De lijst hierboven is niet theoretisch - elk van deze fouten heeft ergens productie problemen veroorzaakt. De vraag is niet óf je ze tegenkomt, maar of je ze voor of na launch ontdekt.

Bouw je zelf een SaaS? Neem contact op voor een architectuur review voordat je gaat live. Twee uur voor oplevering voorkomt twee maanden refactoring erna.

Nick van Iersel

Auteur van deze site

Nick van Iersel

Full-stack Developer & IT Consultant

Nick is een Nederlandse full-stack developer en IT consultant uit Waalwijk met ruim zes jaar ervaring in productie-software. Hij richt zich op Next.js, TypeScript, React en Node.js voor websites, SaaS platformen en mobile apps, en werkt zowel op uurbasis als op projectbasis met vaste prijs.

Ready to make your idea a reality?

Let's build something amazing together