ShipFast to TurboStarter migration guide
Migrate ShipFast to TurboStarter without losing users, subscriptions, or product logic. Follow a staged plan for data, auth, billing, and cutover.

Moving a live SaaS from ShipFast to TurboStarter is not a package upgrade. You are moving product code between two different foundations: ShipFast's single Next.js app and TurboStarter's Turborepo with shared packages for auth, billing, database, API, email, and UI.
That makes a staged migration safer than copying folders until the build turns green.
Short answer
The safest ShipFast to TurboStarter migration starts with a clean TurboStarter repository, then ports your product schema and features one vertical slice at a time. Preserve external identifiers such as Stripe customer and subscription IDs, migrate users into Better Auth's schema, expect existing sessions to expire, and switch production traffic only after old and new systems return the same access decisions.
This guide covers the practical path for ShipFast's MongoDB and Supabase variants. It assumes you already have product code or live customers worth preserving. If you have not launched yet, skip the data-transfer steps and move only your UI, copy, and domain logic.
What actually changes when you leave ShipFast?
A ShipFast migration changes architecture, not only branding. The official ShipFast setup guide documents a familiar single-app layout with /app, /app/api, /components, /libs, and /models. TurboStarter separates deployable apps from reusable infrastructure under apps/ and packages/.
| Concern | ShipFast source | TurboStarter destination | Migration rule |
|---|---|---|---|
| Pages and layouts | /app | apps/web/src/app | Port route groups, then adapt imports |
| Product UI | /components | apps/web/src/modules or packages/ui | Keep app-specific UI local; share primitives |
| API routes | /app/api | packages/api plus web adapters | Move business rules before deleting old routes |
| Database | /models or Supabase schema | packages/db/src/schema | Define Drizzle schema first, then import data |
| Authentication | NextAuth route and helpers | packages/auth | Migrate users/accounts; invalidate old sessions |
| Billing | Stripe/Lemon Squeezy helpers and webhooks | packages/billing and packages/billing-web | Preserve provider IDs and active subscriptions |
/libs and templates | packages/email | Port templates, sender domains, and event triggers | |
| Product configuration | config.js | typed config plus environment files | Map values deliberately instead of copying |
ShipFast remains a good fit for a lean, web-only maker product. The reason to migrate is usually structural: you now need organizations, roles, operator tooling, deeper auth, a typed shared API, or future web, mobile, and extension clients. If that is not your roadmap, a migration may create work without creating product value.
For the product-level tradeoffs, read TurboStarter vs ShipFast. The rest of this article focuses on execution.
Before touching code: choose the migration mode
Your current product state determines the risk.
| Product state | Recommended mode | Expected user impact |
|---|---|---|
| Not launched | Fresh port | None; move only code and seed data |
| Private beta, no paid users | Fresh database plus user re-invite | Users sign in again |
| Live with paid users | Staged migration and reconciliation | Planned sign-in refresh; no subscription recreation |
| High write volume | Dual-write or short maintenance window | Depends on cutover design |
Do not rewrite in place
Keep ShipFast deployable while TurboStarter is being prepared. A second repository, database branch, and preview deployment give you a rollback path. Replacing auth, database access, and billing webhooks inside the production repository at once removes that safety.
Create a migration ledger before coding. For every ShipFast feature, record its route, database collections or tables, environment variables, third-party integrations, and destination module. Also record the owner and a verification check. "Dashboard migrated" is vague. "Existing paid user sees Pro dashboard and can open Stripe portal" is testable.
Step 1: freeze and inventory the ShipFast app
Start from a known production commit. Pause unrelated refactors until cutover, because every change made in the old app creates another change to replay.
Inventory these surfaces:
- Routes: public pages, dashboard pages, dynamic routes, API endpoints, cron handlers
- Data: users, leads, product records, usage, credits, access flags, provider IDs
- Auth: Google OAuth, magic links, any custom credentials, callbacks, session strategy
- Billing: products, prices, customers, subscriptions, one-time orders, webhook events
- Integrations: Resend or Mailgun, analytics, support, storage, monitoring
- Configuration: every key in
config.js,.env.local, Vercel, and provider dashboards - Background behavior: webhook side effects, scheduled jobs, retry logic, welcome emails
Take backups before writing migration scripts. For MongoDB, create an Atlas snapshot or a consistent export. For Supabase, create a database backup and verify that you can restore it. Keep a separate export of the provider identifiers you cannot reconstruct locally:
user_id
email
stripe_customer_id
stripe_subscription_id
stripe_price_id
subscription_status
current_period_endShipFast's subscription tutorial uses a hasAccess or has_access boolean as the simple entitlement signal. Do not treat that boolean as the billing source of truth during migration. Reconcile it against Stripe or Lemon Squeezy and the active subscription records first.
Step 2: bootstrap TurboStarter beside the old app
Clone TurboStarter into a separate directory and give it its own origin. The official repository setup guide also recommends keeping the TurboStarter repository as an upstream remote so future kit updates remain pullable.
git clone git@github.com:turbostarter/core my-product-next
cd my-product-next
git remote rm origin
git remote add upstream git@github.com:turbostarter/core
git remote add origin <your-new-repository-url>
pnpm install
pnpm services:setup
pnpm devDo not begin by deleting mobile or extension code. First get the untouched repository running, commit that baseline, and learn the project structure. You can keep a web-only product in apps/web; the extra apps do not need to be deployed.
Next, create a preview environment with:
- a non-production database
- separate OAuth callback URLs
- Stripe or Lemon Squeezy test mode
- a staging email domain or recipient allowlist
- production-like environment variable names
TurboStarter separates shared variables at the repository root from app-specific variables under apps/web. Follow the environment variable guide rather than pasting ShipFast's .env.local into both locations.
Step 3: move configuration, not secrets
Map each ShipFast setting to one of three destinations:
| ShipFast value | TurboStarter destination |
|---|---|
| Product name, URL, locale | shared root environment |
| Public theme and web behavior | apps/web configuration or NEXT_PUBLIC_* |
| Database, auth, billing, email secrets | local secret file and deployment secret store |
Common renames include:
# ShipFast
NEXTAUTH_URL=
NEXTAUTH_SECRET=
GOOGLE_ID=
GOOGLE_SECRET=
MONGODB_URI=
# TurboStarter equivalents and destination concepts
URL=
BETTER_AUTH_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
DATABASE_URL=Use the variables present in your purchased TurboStarter version as the authority. The mapping above describes concepts, not a complete .env file. Provider-specific billing and email variables should be copied from their dashboards, never from committed source.
Rotate auth secrets during cutover. Reusing NEXTAUTH_SECRET as BETTER_AUTH_SECRET does not preserve NextAuth sessions, and it creates a false expectation that the cookie formats are compatible.
Step 4: define the destination data model
Do not shape TurboStarter's database around a raw MongoDB export. First model the product you want to maintain.
TurboStarter keeps domain schemas in packages/db/src/schema. Its Drizzle schema guide groups related tables by domain, while generated SQL migrations give you a reviewable history. Add your product tables beside the kit's existing auth and billing tables.
Separate users, organizations, memberships, subscriptions, and your product data. Replace embedded MongoDB documents with tables only where independent querying or constraints justify it.
Preserve your existing product IDs where practical. If IDs must change, create an explicit old_id -> new_id map and keep it until every foreign key, webhook reference, and support workflow is reconciled.
Encode unique emails, provider IDs, ownership, and common query paths in the schema. Do not rely on the import script to keep data valid forever.
pnpm with-env turbo db:generate
pnpm with-env pnpm --filter @workspace/db db:migrateReview the generated migration before applying it outside local development. TurboStarter's migration docs recommend migration files for production and reserve direct db:push for local prototyping.
MongoDB to PostgreSQL
Treat this as an ETL job: export, normalize, validate, load.
- Convert
ObjectIdvalues consistently, either preserving their string form or mapping them to new IDs - Expand embedded arrays only when they represent real relations
- Convert missing fields deliberately instead of letting
undefined,null, and empty strings collapse together - Parse dates before insertion and reject invalid timestamps
- Import parents before children
- Make the script idempotent with upserts or a migration-run ledger
Run the importer against a restored snapshot, never directly against the only production database. Compare record counts and sample records by ID after every table.
Supabase PostgreSQL to TurboStarter PostgreSQL
You may be able to keep the same Supabase project because TurboStarter supports a normal Postgres connection and documents a Supabase database recipe. That does not mean you should point the new app at production immediately.
Create a branch or restored copy first. Add TurboStarter's schema, write SQL or TypeScript transforms for existing profiles and product tables, then test both reads and constraints. If ShipFast used Supabase Auth rather than NextAuth in your edition, treat that as a separate auth-provider migration and verify identities against Supabase's auth schema.
Step 5: migrate NextAuth users to Better Auth
This is the part most likely to lock customers out. TurboStarter uses Better Auth, while ShipFast's documented NextAuth setup supports Google OAuth and magic links.
The official Auth.js to Better Auth migration guide documents the schema differences. Better Auth separates users, accounts, and sessions. Credential passwords, when present, belong in an account record with providerId: "credential".
For ShipFast's standard Google and magic-link flow:
- Import the user profile with a stable ID, normalized email, name, image, and verification state
- Map OAuth accounts to the Better Auth account table using the provider and provider account ID
- Do not import verification tokens that have already expired
- Decide whether to import active sessions or intentionally invalidate them
- Configure the same Google project with TurboStarter's new callback URL
- Send a pre-cutover notice that users may need to sign in again
A forced re-login is usually the safer choice
NextAuth and Better Auth do not share a session-cookie format. For most SaaS migrations, preserving users and linked OAuth accounts while expiring old sessions is simpler to audit than translating live session tokens. Test Google and magic-link sign-in against migrated accounts to ensure they attach to an existing user rather than creating duplicates.
TurboStarter's auth configuration supports password, magic link, OTP, passkeys, anonymous sessions, and OAuth. Match the initial production behavior to ShipFast first. Add MFA, passkeys, and organization flows only after the migration is stable.
Verification queries should catch:
- duplicate normalized emails
- users without an account for their enabled login method
- OAuth accounts pointing to missing users
- unexpected new users created during sign-in tests
- admin or role fields that changed meaning
Step 6: preserve billing relationships
Do not cancel and recreate subscriptions. Stripe and Lemon Squeezy are external systems of record, and existing customer/subscription IDs should survive the codebase migration.
TurboStarter provides a unified billing layer for Stripe, Lemon Squeezy, Polar, and Dodo Payments. Configure the provider you already use first, using the billing overview and provider-specific guide.
Map these records into TurboStarter's billing tables:
- provider customer ID
- subscription ID
- product or variant ID
- price ID
- status
- current period boundaries
- trial boundaries
- the local user or organization reference that owns billing
Then implement a reconciliation job:
for each migrated billing reference:
fetch provider customer and active subscriptions
compare external IDs, status, price, and period dates
update destination only when the provider confirms the value
log mismatches for manual reviewDuring staging, point a provider test webhook at TurboStarter and replay representative events: checkout completed, renewal paid, payment failed, subscription updated, and subscription canceled. Verify that retries do not create duplicate records. Stripe's official webhook guide covers local forwarding, signature verification, retries, and event ordering.
At production cutover, create the new webhook endpoint before disabling the old one. Avoid leaving both handlers with mutating side effects for long. A duplicate welcome email is annoying; duplicate credit grants or entitlement changes are a billing incident.
If you are adding organizations, keep migrated subscriptions attached to personal accounts initially. Move billing ownership to organizations in a separate, user-visible migration after the technical cutover. TurboStarter supports both personal and organization billing.
Step 7: port one product slice end to end
Folder-by-folder migration hides broken boundaries. Move a complete user outcome instead.
For example, migrate "create project":
- Add the Drizzle project table
- Add its validated API mutation and query
- Port the dashboard form and list
- Enforce ownership in the API
- Add unit and browser tests
- Verify the same account sees the same projects in both systems
Repeat for each product capability. Keep app-specific screens in apps/web/src/modules; put reusable server logic in packages/api, data access in packages/db, and generic primitives in packages/ui.
ShipFast API routes often combine session lookup, request parsing, database access, and response formatting in one file. When porting them, separate:
- Zod input validation
- protected API procedure or route
- domain service
- database query
- UI query/mutation hook
The build-a-feature recipe shows the intended TurboStarter flow. This is where the migration pays back its cost: mobile and extension clients can call the shared API later without duplicating the feature.
Step 8: migrate the product shell last
Once a vertical slice works, port the visual shell:
- Tailwind theme and design tokens
- logos, fonts, and static assets
- landing and pricing copy
- dashboard navigation
- legal pages
- blog and SEO metadata
- transactional email templates
Do not copy ShipFast's root layout, providers, or authentication buttons wholesale. Keep TurboStarter's provider tree and replace presentation around it. The same rule applies to checkout: preserve the TurboStarter billing client and style the trigger, rather than transplanting ShipFast's ButtonCheckout and its old API assumptions.
For content, move articles into TurboStarter's content collections and preserve public slugs. Add redirects for every URL that changes. A code migration should not erase accumulated backlinks or customer bookmarks.
Step 9: rehearse the production cutover
Run at least one full rehearsal from a recent production backup.
Restore ShipFast data into an isolated migration source and create an empty destination from the current TurboStarter migrations.
Run user, account, product, and billing imports with structured logs. Record duration and failures.
Compare counts, sampled records, totals, orphaned relations, and provider billing state.
Sign in, open the dashboard, create and edit core data, start checkout, open the billing portal, receive email, and sign out.
Switch the test domain back to ShipFast and confirm no destination-only writes are required to restore service.
Your release checklist should include:
- Database backup and restore tested
- Import scripts are repeatable
- User and account counts reconcile
- Existing Google and magic-link users can sign in
- Paid, trialing, past-due, and canceled access states match
- Stripe/Lemon Squeezy portal opens for migrated customers
- Webhook signatures and retries work
- Core product journeys pass E2E tests
- Old URLs redirect correctly
- Monitoring and support contacts are active
- Rollback owner and decision deadline are named
Step 10: cut over with a bounded write freeze
For a typical early-stage SaaS, the simplest reliable cutover is a short maintenance window:
- Put ShipFast into read-only or maintenance mode
- Take the final backup/export
- Run the proven migration scripts
- Reconcile users, product data, and billing
- Deploy TurboStarter
- Update OAuth callbacks and the production billing webhook
- Switch the domain
- Run smoke tests from a real external browser
- Monitor auth failures, webhook errors, and support messages
- Keep ShipFast deployable until the rollback window closes
If you cannot pause writes, implement change capture or dual writes before cutover. That is more engineering than most small SaaS products need. Do not claim "zero downtime" while silently accepting lost writes between export and DNS switch.
Common migration failures
- Copying the whole
/appdirectory: this overwrites TurboStarter's layouts, providers, and route conventions before the new infrastructure works. - Recreating paid subscriptions: customers can be charged twice or lose their current billing period. Preserve provider IDs.
- Matching users only by email: OAuth provider IDs and email normalization matter. Test account linking explicitly.
- Migrating
hasAccessas truth: access booleans drift. Reconcile against provider subscription state. - Enabling every new auth feature immediately: MFA, passkeys, and organizations expand the test matrix. Reach parity first.
- Pointing both webhook handlers at production: duplicate side effects are easy to trigger and hard to unwind.
- No rollback rehearsal: a backup is not a rollback plan until someone has restored it.
Frequently asked questions
No. There is no safe one-command migration because the kits use different repository, auth, database, and billing abstractions. The repeatable parts are data export/import and verification scripts. Product UI and business logic still need to be placed in TurboStarter's modules and shared packages deliberately.
Yes. Keep the same Stripe account, customers, subscriptions, products, and price IDs. Import their identifiers into TurboStarter's billing records, reconcile status through Stripe, and move the webhook endpoint during cutover. Do not ask customers to subscribe again.
Usually yes. ShipFast's NextAuth sessions and TurboStarter's Better Auth sessions are not interchangeable. Preserve user and OAuth account records, invalidate old sessions, and test that the first Better Auth login attaches to the existing user instead of creating a duplicate.
Yes. TurboStarter uses PostgreSQL through Drizzle and can connect to Supabase Postgres. Use a database branch or restored copy to add the TurboStarter schema and transform existing data before changing the production connection.
A prelaunch code port may take a few focused days. A live product with custom data, users, subscriptions, and integrations usually takes longer because rehearsals and reconciliation matter more than copying code. Estimate by counting vertical product slices and external integrations, not source files.
Yes. First reach functional parity for your current web product. Then add organizations, role-based access, mobile, or a browser extension as separate changes. Combining foundation migration with a new tenancy model makes data ownership and billing failures much harder to diagnose.
ShipFast to TurboStarter migration: the decision
A ShipFast to TurboStarter migration is worth doing when your product has outgrown a single web app and the next roadmap items depend on shared infrastructure: teams, RBAC, admin support workflows, richer Better Auth features, or additional clients.
The winning strategy is not a heroic rewrite. Keep ShipFast stable, establish TurboStarter as a clean destination, move one product slice at a time, and treat users plus subscriptions as data-migration projects with explicit reconciliation.
If that matches your roadmap, explore the TurboStarter architecture, compare TurboStarter and ShipFast, and use the web documentation as the destination contract for each module.
Self-host your Next.js Turborepo app with Docker in 5 minutes
Learn how to containerize your Next.js Turborepo app with Docker, optimize the image, and deploy it to any environment in minutes with practical examples.
Software versioning for web, mobile, and browser extensions
Software versioning strategies for SaaS: SemVer, app versioning on stores, OTA vs binaries, and how to version web, mobile, and extensions without chaos.



