Dare a ogni cliente SaaS il proprio spazio di contenuti
Dai a ogni cliente uno spazio di contenuti isolato con isolamento garantito dalla row-level security di PostgreSQL — non da un'istruzione if a livello applicativo.
Da qualche parte tra il ventesimo e il cinquantesimo cliente, "aggiungiamo semplicemente una
colonna tenant_id" smette di essere un piano e diventa un rischio.
Questo articolo parla dei tre modi per realizzare contenuti multi-tenant, perché uno di
essi è significativamente più sicuro, e di cosa fanno i comuni modelli di prezzo dei CMS
al margine di un SaaS quando il numero di clienti cresce.
Dipende interamente dall'applicazione della regola
Il più basso — una migrazione, un backup
Una query che dimentica il filtro
Quasi tutti finiscono al livello di riga, perché il costo operativo degli altri
due è reale e cumulativo. Il che rende l'ultima cella l'intera questione:
cosa ferma la query che dimentica il filtro?
Non perché il tuo team sia sbadato. Per aritmetica.
Un prodotto maturo ha centinaia di query. Alcune sono nel job di reporting che nessuno
ha aperto da un anno. Alcune sono nello strumento di amministrazione scritto durante un incidente.
Alcune sono in un modulo aggiunto da un collaboratore esterno e revisionato in fretta. Ognuna
di esse deve ricordarsi WHERE tenant_id = $1.
Il fallimento è silenzioso e totale. Un filtro mancante non genera un errore. Restituisce
i contenuti di un cliente a un altro, in una risposta che sembra perfettamente normale
finché qualcuno non nota la pagina di prezzi non pubblicata del proprio concorrente in un
menù a tendina.
La row-level security di PostgreSQL sposta il confine al di sotto dell'errore.
Colleghi una policy alla tabella, imposti il tenant nella sessione, e il
database applica il predicato a ogni query — comprese quelle che nessuno
ha revisionato:
Ora la query che ha dimenticato il filtro restituisce zero righe invece di quelle di tutti
gli altri. Il bug diventa un'assenza visibile invece di una fuga invisibile — e
quella differenza è l'intero argomento.
corpusctl fa questo nel core, accanto all'auth e al registro dei moduli. Ogni
query applicativa è comunque limitata al tenant in modo esplicito; la RLS è la seconda linea
di difesa, non la prima. Il senso di una seconda linea è che sia lì il
giorno in cui la prima fallisce.
Le tabelle sono anche partizionate per hash in base al tenant, il che impedisce a un grande cliente di
degradare i piani di esecuzione delle query per tutti gli altri.
L'errore di modellazione che costa di più in seguito.
In un vero SaaS, le persone appartengono a più clienti. Un utente di un'agenzia gestisce
tre dei tuoi clienti. Un tecnico del supporto ha bisogno dell'accesso in lettura a un account per
una settimana. Un collaboratore esterno lavora sul brand due e non deve vedere il brand uno.
Se il tuo modello è "l'utente appartiene al tenant", ognuno di questi diventa un
account duplicato, e gli account duplicati diventano un problema di audit.
Il modello di corpusctl: un utente porta con sé un ruolo separato in ogni spazio. Proprietario su
uno, editor su un altro, invisibile su un terzo. Un'identità, N appartenenze, e
il pannello mostra solo gli spazi a cui la persona appartiene effettivamente.
C'è una regola deliberata in cima: l'ultimo proprietario di uno spazio non può essere
retrocesso o rimosso. Uno spazio senza proprietario è uno spazio che nessuno può amministrare —
il lucchetto resta all'esterno.
#Cosa fa il prezzo per progetto al margine di un SaaS
È qui che la scelta del CMS smette di essere una decisione tecnica.
Verificato dalle pagine dei prezzi dei fornitori il 17 agosto 2026:
Prodotto
Unità
Prezzo
100 clienti
Strapi Cloud
Per progetto
da 35 $/mese
da 3.500 $/mese
Sanity
Per dataset oltre 2
999 $/mese
proibitivo
Directus
Per istanza / postazione
499 $/mese Team, +50 $/postazione
operazioni per istanza
Ghost(Pro)
Per pubblicazione, in base al pubblico
da 18 $/mese annuale
100 abbonamenti
corpusctl
Nessuna
Piano fisso
invariato
Il pattern da notare: quelle unità scalano con il tuo numero di clienti, non con
il tuo ricavo per cliente. Un SaaS su un piano da 29 $/mese non può assorbire una voce di CMS da 35 $/mese
per cliente. L'aritmetica semplicemente non torna, e non
migliora con il volume.
È questo il caso per cui corpusctl è stato costruito. Gli spazi non sono un'unità di fatturazione.
#Le letture, e perché nemmeno queste dovrebbero essere contate
Il secondo costo che scala con i clienti è il traffico. Se il tuo CMS conta le chiamate
API, allora i visitatori di ogni cliente sono sul tuo contatore, e il tuo cliente più
trafficato è il tuo più costoso — a prescindere da quanto ti paga.
Il percorso di lettura di corpusctl elimina il contatore eliminando la chiamata. La pubblicazione scrive
il contenuto in un file immutabile e indirizzato tramite hash nell'object storage e aggiorna
un manifest. Il client del lettore risolve lo slug in uno shard del manifest localmente,
poi recupera quell'indirizzo dalla cache edge nginx. L'indirizzo è un hash del
contenuto, quindi non può mai restituire contenuti diversi — memorizzabile nella cache per trenta giorni senza
alcuna strategia di invalidazione.
Il traffico dei visitatori non raggiunge mai l'API o PostgreSQL. Il tuo percorso di scrittura è dimensionato
per i tuoi editor; il tuo percorso di lettura è dimensionato dalla tua cache.
In un SaaS avrai clienti che fanno downgrade, decadono e tornano. Ciò che il
CMS fa in quel momento è una decisione di prodotto che erediti.
corpusctl rende ogni funzionalità un modulo — media, ricerca, webhook, SEO,
GraphQL, import, analytics — con due stati:
Stato
Dati
disabled
Preservati. Riattivalo e riprende da dove si era interrotto.
uninstalled
onUninstall li cancella, con una doppia conferma.
Un downgrade del piano imposta i moduli su disabled. Non elimina mai i dati. Questo è
imposto nel codice, non promesso in un articolo di supporto — il che conta,
perché il cliente che torna dopo tre mesi si aspetta che i suoi contenuti
siano lì, e ha ragione ad aspettarselo.
Inoltre i moduli non possono importarsi a vicenda. Un service registry e un event bus
sono gli unici canali tra di loro, e la CI fa fallire la build se quella regola viene
infranta. La conseguenza pratica: rimuovere una funzionalità non può rompere il sistema,
quindi i set di funzionalità per tenant sono una questione di configurazione anziché un fork.
#Una checklist per valutare qualsiasi CMS per SaaS multi-tenant
Un progetto è un'unità di fatturazione? Quanto costa il cliente numero 100?
Dove viene applicato l'isolamento — nell'applicazione o nel database?
Un utente può avere ruoli diversi in tenant diversi?
Le letture sono contate? Da chi — da te o dai visitatori dei tuoi clienti?
Cosa succede ai dati al downgrade?
Puoi provisionare un tenant dall'API, senza un umano nel pannello?
C'è un contatore di quota per tenant che puoi leggere e su cui fatturare?
La prima domanda elimina la maggior parte dei candidati prima ancora di arrivare alla seconda.
Un CMS headless che si adatta a Next.js invece di combatterlo
Modalità bozza, ISR, l'App Router e lo strato di rendering. Cosa conta davvero quando colleghi un CMS headless a Next.js — e i tre modi in cui va storto.