Hoe migreer ik een bestaande SaaS naar supastack?
Dat hangt af van je codebase, maar de volgorde verandert niet: migreer in fasen, nooit in één rewrite. Inventariseer de repo in productcode (routes, schema, domeinlogica — die blijft van jou) en infrastructuur (auth, tenancy, billing, jobs, e-mail — die vervangt de kit). Neem modules over in afhankelijkheidsvolgorde: structuur, auth, workspaces, billing en daarna achtergrondwerk. Verplaats data met een opnieuw uitvoerbaar script, behoud Stripe-klant-ID's en schakel over zodra de acceptatie slaagt. De meeste webapps kosten dagen aan avonden, geen weken.
Inventariseer voordat je code aanraakt
Elke migratie die misgaat begon hetzelfde: iemand opende de kit-repo en begon bestanden te verwijderen. De kit is een fundering, geen thema — hij vervangt je infrastructuur en heeft geen mening over je product. Voordat je iets verhuist, splits je repository eerlijk in twee stapels.
Productcode is alles wat een klant aan je zou kunnen terugvertellen: je routes, je schema, je domeinlogica, je teksten, je prijzenpagina. Infrastructuur is alles wat ze nooit zien: sessies, tenancy-checks, betalingswebhooks, jobs in de wachtrij, transactionele mail. De eerste stapel migreert intact. De tweede wordt vervangen — en dat is precies de bedoeling.
- Som elke routegroep op en label hem als product of plumbing — wees genadeloos; een instellingenpagina die alleen een webhook-URL omzet is plumbing.
- Som je env-vars op en markeer welke infrastructuur configureren (auth-secrets, providersleutels) versus productgedrag.
- Som je tabellen op en markeer welke de kit gaat bezitten (users, sessies, workspaces, billing-status) versus welke van jou zijn.
- Schrijf de ene user journey op die je betaalt — die flow is de acceptatietest voor elke fase hieronder.
Koppel elke naad aan een kit-module
De infrastructuur van de kit is een set modules met contracten ertussen — jouw taak is elk stuk van je oude plumbing naar de module te wijzen die er nu eigenaar van is. Het meeste van die mapping is een-op-een; waar dat niet zo is, neem je de echte beslissingen.
Twee mappingkeuzes verdienen echt nadenken in plaats van mechanisch porteren. Kom je van Prisma, dan standaardiseert de kit op Drizzle — je mag Prisma voor producttabellen aanhouden tijdens de overgang, maar plan om te convergeren; twee ORM's in één codebase is een onderhoudstaks die zich opstapelt. De Prisma vs Drizzle-vergelijking zet uiteen wat er verandert.
En kom je van een managed-authvendor, weersta dan de drang om elke vendorfeature vóór de overstap te repliceren. Managed auth bundelt stilletjes dingen die je product misschien niet gebruikt — sessielijsten, organisatie-UI's, webhook-sync. Map wat je product echt aanroept; de auth-vergelijking behandelt de verschillen in mogelijkheden die ertoe doen.
| Je huidige plumbing | supastack-module | Wat meeverhuist |
|---|---|---|
| NextAuth.js / Auth.js-sessies | Authenticatie (Better Auth) | Users en accounts migreren; sessies worden bij de overstap opnieuw uitgegeven |
| Clerk of een managed-authvendor | Authenticatie (Better Auth) | Users migreren per e-mail; sessies van derden worden achtergelaten, niet opnieuw afgespeeld |
| Zelfgebouwde org-/teamtabellen | Werkruimtes | Je org-rijen worden workspace- + lidmaatschapsrijen |
| Handgemaakte Stripe-webhooks | Facturatie | Stripe-klant-ID's blijven bestaan; abonnementsstatus wordt opnieuw afgeleid |
| CRON- + setTimeout-jobs | Taken (Trigger.dev) | Gepland werk verhuist achter het gedeelde JobRunner-contract |
| Nodemailer / ad-hocmail | Templates verhuizen mee; verzenden loopt via één provideradapter |
Neem modules over in afhankelijkheidsvolgorde
Migratievolgorde is geen voorkeur — het is een dependency-graaf. Elke fase hieronder is apart te shippen en levert een werkend systeem op dat je kunt verifiëren voordat de volgende begint. De verleiding is om alles in één weekend te doen en de doorsnede te debuggen; weersta hem — een gefaseerde migratie betekent dat elke bug die je tegenkomt bij precies één module hoort, een debug-superkracht die je pas waardeert als je hem overslaat.
- Fase 1 — skelet: scaffold de kit, configureer env, laat je productroutes in de projectstructuur vallen. Er is nog niets aangesloten; de app start en je pagina's renderen.
- Fase 2 — auth: verhuis inloggen naar Better Auth, migreer de user-/accounttabellen, verifieer in- en uitloggen van begin tot eind. Elke latere fase hangt af van het bestaan van een sessie.
- Fase 3 — tenancy: neem het workspacemodel over, backfill
workspaceIdop je producttabellen, dwing scoping af in reads en writes. Hier verdient het artikel over workspace-grenzen zich terug. - Fase 4 — facturatie: richt checkout en webhooks op de facturatiemodule, wijs bestaande abonnementen opnieuw toe, verifieer entitlement-checks tegen echte Stripe-data in testmodus.
- Fase 5 — achtergrondwerk en e-mail: port jobs naar het JobRunner-contract en verhuis transactionele mail naar de e-mailadapter. Deze komen als laatste omdat ze afhangen van users en workspaces die al bestaan.
Verhuis de data eerlijk
Datamigratie is waar migraties hun reputatie verdienen. De regels die het saai houden: schrijf een script dat je twee keer kunt draaien, muteer de bron nooit vóór de overstap en verifieer aantallen in plaats van het script te vertrouwen.
Users en accounts landen in het Better Auth-schema — e-mail is de join-sleutel, en accounts op basis van wachtwoorden vragen een beslissing: of je bestaande hashformaat is er een dat Better Auth kan verifiëren, of je shippt bij de overstap een reset-wachtwoordflow en behandelt die als forcing function. OAuth-accounts van derden zijn het makkelijke geval — de provider geeft ze bij de volgende login opnieuw uit.
Voor facturatie is de eerlijke weg opnieuw afleiden in plaats van rijen kopiëren: behoud de Stripe-klant-ID, laat de webhookafhandeling van de kit de entitlement-status herbouwen uit de waarheid van de provider en diff wat hij berekent tegen je bestaande abonnementsrecords. Het idempotente webhook-grootboek van de kit bestaat precies zodat opnieuw afgespeelde historische events niets dubbel provisioneren.
// 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}`);
}Wat van jou blijft
Het is de moeite om het ronduit te zeggen, want dit is het deel dat mensen aan beide kanten verkeerd doen: je productroutes, je schema, je domeinlogica en je ontwerp veranderen niet. Je herbouwt je app niet binnen de kit — je zet je app er bovenop neer.
De omgekeerde fout is oude plumbing 'tijdelijk' naast de nieuwe module laten leven. Een tweede sessiecheck, een tweede webhook-handler, een tweede entitlement-tabel — elk 'tijdelijk' parallelsysteem is permanent tot iemand het verwijdert, en elk is een toekomstig incident. Vervang, verifieer, verwijder. Als een fase het oude pad nog niet kan verwijderen, is hij niet klaar.
De overstapchecklist
De overstap is een checklist, geen gebeurtenis. Draai hem minstens één keer in een stagingomgeving met data in productievorm vóór de echte datum.
- Draai het datascript opnieuw tegen een verse productiesnapshot; aantallen en steekproeven slagen.
- Log in met elke authprovider die je product ondersteunt — inclusief degene waarvan je vergeten was dat ze bestonden.
- Koop je eigen product in Stripe-testmodus en volg de webhook door tot de entitlement.
- Stuur elke transactionele e-mail eenmalig; controleer dat links op de nieuwe deployment landen.
- Verifieer de betalende journey van begin tot eind, zet dan DNS/env om en bekijk de eerste echte sessie.
- Houd de oude deployment een week draaiend — rollback is een DNS-wijziging, geen migratie.
Veelgestelde vragen over migreren naar een starterkit
Dezelfde handvol vragen komt op telkens als iemand hiervoor gaat zitten.
- Hoe lang duurt een migratie echt? — Voor een enkele webapp met standaard-auth + Stripe: dagen aan avonden, geen weken. Multi-tenant B2B met custom billing voegt tijd toe in fase drie en vier — het is het datawerk dat uitloopt, nooit het verplaatsen van bestanden.
- Moet ik migreren of opnieuw beginnen en features porteren? — Als je productcode kleiner is dan je infrastructuurschuld, is opnieuw beginnen op de kit en productroutes porteren vaak sneller. Zijn je schema en domeinlogica het moeilijke deel, migreer dan ter plaatse.
- Kan ik mijn bestaande ORM / auth-library tijdens de overgang behouden? — Even, ja — één modulegrens tegelijk. Permanent, nee: twee van wat dan ook in de infrastructuurlaag is een zich opstapelende taks.
- En mijn Stripe-webhooks die al onderweg zijn? — Richt het bestaande webhook-endpoint op de handler van de kit zodra fase vier geverifieerd is; het idempotente grootboek maakt opnieuw afgespeelde of gedupliceerde events veilig in plaats van gevaarlijk.
- Moet ik elke module overnemen? — Nee. Mobiel, desktop, jobs en AI zijn oppervlakken om over te nemen of te negeren — de licentie is de fundering, geen mandaat om per platform een app te shippen.