10+ AI SaaS templates for web & mobile
home

ShipFast to TurboStarter migration guide

·17 min read

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/.

ConcernShipFast sourceTurboStarter destinationMigration rule
Pages and layouts/appapps/web/src/appPort route groups, then adapt imports
Product UI/componentsapps/web/src/modules or packages/uiKeep app-specific UI local; share primitives
API routes/app/apipackages/api plus web adaptersMove business rules before deleting old routes
Database/models or Supabase schemapackages/db/src/schemaDefine Drizzle schema first, then import data
AuthenticationNextAuth route and helperspackages/authMigrate users/accounts; invalidate old sessions
BillingStripe/Lemon Squeezy helpers and webhookspackages/billing and packages/billing-webPreserve provider IDs and active subscriptions
Email/libs and templatespackages/emailPort templates, sender domains, and event triggers
Product configurationconfig.jstyped config plus environment filesMap 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 stateRecommended modeExpected user impact
Not launchedFresh portNone; move only code and seed data
Private beta, no paid usersFresh database plus user re-inviteUsers sign in again
Live with paid usersStaged migration and reconciliationPlanned sign-in refresh; no subscription recreation
High write volumeDual-write or short maintenance windowDepends 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:

  1. Routes: public pages, dashboard pages, dynamic routes, API endpoints, cron handlers
  2. Data: users, leads, product records, usage, credits, access flags, provider IDs
  3. Auth: Google OAuth, magic links, any custom credentials, callbacks, session strategy
  4. Billing: products, prices, customers, subscriptions, one-time orders, webhook events
  5. Integrations: Resend or Mailgun, analytics, support, storage, monitoring
  6. Configuration: every key in config.js, .env.local, Vercel, and provider dashboards
  7. 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_end

ShipFast'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 dev

Do 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 valueTurboStarter destination
Product name, URL, localeshared root environment
Public theme and web behaviorapps/web configuration or NEXT_PUBLIC_*
Database, auth, billing, email secretslocal 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:migrate

Review 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 ObjectId values 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:

  1. Import the user profile with a stable ID, normalized email, name, image, and verification state
  2. Map OAuth accounts to the Better Auth account table using the provider and provider account ID
  3. Do not import verification tokens that have already expired
  4. Decide whether to import active sessions or intentionally invalidate them
  5. Configure the same Google project with TurboStarter's new callback URL
  6. 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 review

During 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":

  1. Add the Drizzle project table
  2. Add its validated API mutation and query
  3. Port the dashboard form and list
  4. Enforce ownership in the API
  5. Add unit and browser tests
  6. 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:

  1. Put ShipFast into read-only or maintenance mode
  2. Take the final backup/export
  3. Run the proven migration scripts
  4. Reconcile users, product data, and billing
  5. Deploy TurboStarter
  6. Update OAuth callbacks and the production billing webhook
  7. Switch the domain
  8. Run smoke tests from a real external browser
  9. Monitor auth failures, webhook errors, and support messages
  10. 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

  1. Copying the whole /app directory: this overwrites TurboStarter's layouts, providers, and route conventions before the new infrastructure works.
  2. Recreating paid subscriptions: customers can be charged twice or lose their current billing period. Preserve provider IDs.
  3. Matching users only by email: OAuth provider IDs and email normalization matter. Test account linking explicitly.
  4. Migrating hasAccess as truth: access booleans drift. Reconcile against provider subscription state.
  5. Enabling every new auth feature immediately: MFA, passkeys, and organizations expand the test matrix. Reach parity first.
  6. Pointing both webhook handlers at production: duplicate side effects are easy to trigger and hard to unwind.
  7. No rollback rehearsal: a backup is not a rollback plan until someone has restored it.

Frequently asked questions

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.

Sounds good?Now let's make it real. In minutes.
Try TurboStarter
world map
Community

Connect with like-minded people

Join our community to get feedback, support, and grow together with 600+ builders on board, let's ship it!

Join us

Ship your startup everywhere. In minutes.

Skip the complex setups and start building features on day one.

Get TurboStarter