Come si migra un SaaS esistente su supastack?
Dipende dalla codebase, ma la sequenza non cambia: migra a fasi, mai in un'unica riscrittura. Fai l'inventario del repo dividendolo in codice di prodotto (route, schema, logica di dominio — resta tuo) e infrastruttura (auth, tenancy, fatturazione, job, email — la sostituisce il kit). Adotta i moduli in ordine di dipendenza: struttura, auth, workspace, fatturazione, poi lavoro in background. Sposta i dati con uno script rieseguibile, conserva i customer ID Stripe e fai il cutover dopo che i controlli di accettazione passano. La maggior parte delle web app richiede giorni di sere, non settimane.
Fai l'inventario prima di toccare il codice
Ogni migrazione che finisce male è iniziata allo stesso modo: qualcuno ha aperto il repo del kit e ha cominciato a cancellare file. Il kit è una fondazione, non un tema — sostituisce la tua infrastruttura e non ha opinioni sul tuo prodotto. Prima di spostare qualsiasi cosa, dividi onestamente il tuo repository in due pile.
Il codice di prodotto è tutto ciò che un cliente potrebbe ridescrivi: le tue route, il tuo schema, la tua logica di dominio, i tuoi testi, la tua pagina dei prezzi. L'infrastruttura è tutto ciò che non vede mai: sessioni, controlli di tenancy, webhook di pagamento, job in coda, email transazionali. La prima pila migra intatta. La seconda viene sostituita — ed è esattamente questo il punto.
- Elenca ogni gruppo di route ed etichettalo come prodotto o plumbing — sii spietato; una pagina di impostazioni che cambia solo l'URL di un webhook è plumbing.
- Elenca le tue env var e segna quali configurano l'infrastruttura (segreti di auth, chiavi dei provider) rispetto al comportamento del prodotto.
- Elenca le tue tabelle e segna quali saranno di proprietà del kit (users, sessions, workspaces, stato di fatturazione) rispetto a quali restano tue.
- Scrivi l'unico user journey che ti paga — quel flusso è il test di accettazione per ogni fase qui sotto.
Mappa ogni giuntura su un modulo del kit
L'infrastruttura del kit è un insieme di moduli con contratti tra loro — il tuo lavoro è puntare ogni pezzo del tuo vecchio plumbing sul modulo che ora lo possiede. La maggior parte del mapping è uno a uno; i punti in cui non lo è sono quelli in cui prendi decisioni vere.
Due scelte di mapping meritano un pensiero vero invece di un porting meccanico. Se vieni da Prisma, il kit standardizza su Drizzle — puoi tenere Prisma per le tabelle di prodotto durante la transizione, ma pianifica di convergere; due ORM in una sola codebase sono una tassa di manutenzione che si accumula. Il confronto Prisma vs Drizzle spiega cosa cambia.
E se vieni da un vendor di auth gestita, resisti all'impulso di replicare ogni funzionalità del vendor prima del cutover. L'auth gestita include silenziosamente cose che il tuo prodotto potrebbe non usare — elenchi di sessioni, UI delle organizzazioni, sync via webhook. Mappa ciò che il tuo prodotto chiama davvero; il confronto sulle auth copre le differenze di capacità che contano.
| Il tuo plumbing attuale | Modulo supastack | Cosa si porta dietro |
|---|---|---|
| Sessioni NextAuth.js / Auth.js | Autenticazione (Better Auth) | Users e account migrano; le sessioni vengono re-emesse al cutover |
| Clerk o un vendor di auth gestita | Autenticazione (Better Auth) | Gli users migrano via email; le sessioni di terze parti sono abbandonate, non ripetute |
| Tabelle org/team personalizzate | Spazi di lavoro | Le tue righe org diventano righe workspace + membership |
| Webhook Stripe fatti a mano | Fatturazione | I customer ID Stripe sopravvivono; lo stato dell'abbonamento viene ricalcolato |
| Job CRON + setTimeout | Job (Trigger.dev) | Il lavoro pianificato passa dietro il contratto JobRunner condiviso |
| Nodemailer / email ad hoc | I template migrano; l'invio passa attraverso un solo adapter di provider |
Adotta i moduli in ordine di dipendenza
L'ordine di migrazione non è una preferenza — è un grafo di dipendenze. Ogni fase qui sotto è rilasciabile separatamente, e ognuna produce un sistema funzionante che puoi verificare prima che inizi la successiva. La tentazione è fare tutto in un weekend e debuggare l'intersezione; resistile — una migrazione a fasi significa che ogni bug che incontri appartiene esattamente a un modulo, un superpotere di debugging che non apprezzerai finché non lo salti.
- Fase 1 — scheletro: fai lo scaffold del kit, configura le env, lascia cadere le route di prodotto nella struttura del progetto. Nulla è ancora cablato; l'app si avvia e le tue pagine renderizzano.
- Fase 2 — auth: sposta il sign-in su Better Auth, migra le tabelle user/account, verifica sign-in + sign-out da un capo all'altro. Ogni fase successiva dipende dall'esistenza di una sessione.
- Fase 3 — tenancy: adotta il modello workspace, fai il backfill di
workspaceIdsulle tue tabelle di prodotto, applica lo scoping in lettura e scrittura. È qui che il post sui confini del workspace si ripaga. - Fase 4 — fatturazione: punta checkout e webhook sul modulo di fatturazione, rimappa gli abbonamenti esistenti, verifica i controlli di entitlement contro dati Stripe reali in modalità test.
- Fase 5 — lavoro in background ed email: porta i job sul contratto JobRunner e sposta le email transazionali sull'adapter email. Arrivano per ultime perché dipendono da users e workspaces già esistenti.
Sposta i dati onestamente
La migrazione dei dati è dove le migrazioni si guadagnano la loro reputazione. Le regole che la mantengono noiosa: scrivi uno script che puoi eseguire due volte, non mutare mai la sorgente prima del cutover e verifica i conteggi invece di fidarti dello script.
Users e account atterrano nello schema di Better Auth — l'email è la chiave di join, e gli account basati su password richiedono una decisione: o il tuo formato di hash esistente è verificabile da Better Auth, oppure rilasci un flusso di reset della password al cutover e lo tratti come un forcing function. Gli account OAuth di terze parti sono il caso facile — il provider li riemette al prossimo sign-in.
Per la fatturazione, il percorso onesto è la re-derivazione invece della copia delle righe: conserva il customer ID Stripe, lascia che la gestione webhook del kit ricostruisca lo stato degli entitlement dalla verità del provider e fai il diff di ciò che calcola contro i tuoi record di abbonamento esistenti. Il ledger idempotente dei webhook del kit esiste proprio perché gli eventi storici riproposti non provisionino nulla due volte.
// migration scripts are boring by design: upsert-shaped,
// re-runnable, and they verify counts instead of trusting themselves
for (const legacyUser of legacyUsers) {
await upsertAuthUser({
email: legacyUser.email.toLowerCase(),
name: legacyUser.displayName,
externalId: legacyUser.id, // preserved for one-shot joins
});
}
const migrated = await countAuthUsers();
if (migrated !== legacyUsers.length) {
throw new Error(`count mismatch: ${migrated} of ${legacyUsers.length}`);
}Cosa resta tuo
Vale la pena dirlo chiaramente, perché è la parte che le persone sbagliano in entrambe le direzioni: le tue route di prodotto, il tuo schema, la tua logica di dominio e il tuo design non cambiano. Non stai ricostruendo la tua app dentro il kit — la stai appoggiando sopra.
L'errore inverso è tenere in vita il vecchio plumbing «temporaneamente» accanto al nuovo modulo. Un secondo controllo di sessione, un secondo handler di webhook, una seconda tabella di entitlement — ogni sistema parallelo «temporaneo» è permanente finché qualcuno non lo cancella, e ognuno è un incidente futuro. Sostituisci, verifica, cancella. Se una fase non può ancora eliminare il vecchio percorso, non è finita.
La checklist del cutover
Il cutover è una checklist, non un evento. Eseguila almeno una volta in un ambiente di staging con dati di forma produzione prima della data vera.
- Riesegui lo script dei dati su uno snapshot di produzione fresco; conteggi e controlli a campione passano.
- Accedi con ogni provider di auth che il tuo prodotto supporta — inclusi quelli di cui avevi dimenticato l'esistenza.
- Compra il tuo stesso prodotto in modalità test di Stripe e segui il webhook fino all'entitlement.
- Invia ogni email transazionale una volta; controlla che i link atterrino sul nuovo deploy.
- Verifica il journey pagante da un capo all'altro, poi gira DNS/env e osserva la prima sessione reale.
- Tieni in esecuzione il vecchio deploy per una settimana — il rollback è un cambio di DNS, non una migrazione.
Domande frequenti sulla migrazione in uno starter kit
La stessa manciata di domande emerge ogni volta che qualcuno si siede per farlo.
- Quanto dura davvero una migrazione? — Per una singola web app con auth standard + Stripe: giorni di sere, non settimane. Il B2B multi-tenant con fatturazione personalizzata aggiunge tempo nelle fasi tre e quattro — è il lavoro sui dati che si allunga, mai lo spostamento dei file.
- Devo migrare o ripartire da zero e portare le funzionalità? — Se il tuo codice di prodotto è più piccolo del tuo debito di infrastruttura, ripartire da zero sul kit e portare le route di prodotto è spesso più veloce. Se il tuo schema e la tua logica di dominio sono la parte difficile, migra sul posto.
- Posso tenere il mio ORM / la mia libreria di auth durante la transizione? — Per poco, sì — un confine di modulo alla volta. Per sempre, no: due di qualsiasi cosa nel livello di infrastruttura è una tassa che si accumula.
- E i miei webhook Stripe già in volo? — Punta l'endpoint webhook esistente sull'handler del kit una volta verificata la fase quattro; il ledger idempotente rende gli eventi riproposti o duplicati sicuri invece che pericolosi.
- Devo adottare ogni modulo? — No. Mobile, desktop, job e AI sono superfici da adottare o ignorare — la licenza è la fondazione, non un mandato a rilasciare un'app per piattaforma.