Migrating from Supastarter to ShipFast
easy
8 hours
nextjs
simplification
payments

Migrating from Supastarter to ShipFast

Simplify your stack by moving from Supastarter to ShipFast when you don't need i18n or complex team features.

Prerequisites

  • Familiarity with Next.js App Router
  • Understanding of your current Supastarter codebase
  • Basic knowledge of Prisma/database migrations

Migrating from Supastarter to ShipFast

Sometimes simpler is better. If you started with Supastarter but found you don't need i18n, multiple payment providers, or complex organization structures, ShipFast offers a leaner alternative that's easier to maintain.

Why Migrate?#

You might want to migrate if:

  • You don't need internationalization (i18n)
  • Single payment provider (Stripe) is sufficient
  • You want a simpler, flatter codebase
  • Team/organization features are overkill for your use case
  • You prefer less abstraction and more direct code

Stay with Supastarter if:

  • You're planning to expand internationally
  • You need flexible payment provider options
  • Multi-tenant/organization features are important
  • You've already built heavily on the organization model

Migration Overview#

AspectSupastarterShipFast
AuthNextAuth with org contextNextAuth (simpler)
PaymentsStripe, Lemon Squeezy, PaddleStripe/Lemon Squeezy
DatabasePrisma + Supabase/PostgresPrisma + various DBs
i18nFirst-class supportNot built-in
TeamsFull organization modelBasic or none
ComplexityHigherLower

Step 1: Set Up a Fresh ShipFast Project#

# Clone or download ShipFast to a new directory
git clone [your-shipfast-repo] my-app-simplified
cd my-app-simplified
npm install

Step 2: Flatten Your Database Schema#

The main change is removing the organization layer:

Supastarter Schema (Complex)#

model User {
  id          String       @id @default(cuid())
  email       String       @unique
  memberships Membership[]
}

model Organization {
  id          String       @id @default(cuid())
  name        String
  memberships Membership[]
  subscription Subscription?
}

model Membership {
  id             String       @id @default(cuid())
  userId         String
  organizationId String
  role           Role
}

ShipFast Schema (Simple)#

model User {
  id           String        @id @default(cuid())
  email        String        @unique
  name         String?
  subscription Subscription? // Direct relationship
}

model Subscription {
  id               String   @id @default(cuid())
  userId           String   @unique
  stripeCustomerId String?
  stripePriceId    String?
  status           String
  user             User     @relation(fields: [userId], references: [id])
}

Migration Script#

// scripts/flatten-to-shipfast.ts
import { PrismaClient as OldPrisma } from './old-prisma-client';
import { PrismaClient as NewPrisma } from '@prisma/client';

const oldDb = new OldPrisma();
const newDb = new NewPrisma();

async function migrateUsers() {
  // Get all users with their primary organization
  const users = await oldDb.user.findMany({
    include: {
      memberships: {
        where: { role: 'OWNER' },
        include: { organization: { include: { subscription: true } } },
      },
    },
  });

  for (const user of users) {
    // Create flattened user
    const newUser = await newDb.user.create({
      data: {
        id: user.id,
        email: user.email,
        name: user.name,
        emailVerified: user.emailVerified,
      },
    });

    // Migrate subscription from organization to user
    const orgSub = user.memberships[0]?.organization?.subscription;
    if (orgSub) {
      await newDb.subscription.create({
        data: {
          userId: newUser.id,
          stripeCustomerId: orgSub.stripeCustomerId,
          stripePriceId: orgSub.stripePriceId,
          status: orgSub.status,
          currentPeriodEnd: orgSub.currentPeriodEnd,
        },
      });
    }
  }

  console.log(`Migrated ${users.length} users`);
}

migrateUsers();

Step 3: Simplify Authentication#

Remove organization context from sessions:

Before (Supastarter)#

callbacks: {
  async session({ session, user }) {
    const membership = await getActiveOrganization(user.id);
    session.user.organizationId = membership?.organizationId;
    session.user.role = membership?.role;
    return session;
  },
},

After (ShipFast)#

callbacks: {
  async session({ session, user }) {
    session.user.id = user.id;
    // No organization context needed
    return session;
  },
},

Step 4: Update API Routes#

Remove organization scoping from your queries:

Before (Supastarter)#

export async function GET(req: Request) {
  const session = await getServerSession(authOptions);
  const { organizationId } = session.user;

  const data = await prisma.project.findMany({
    where: { organizationId },
  });
  return Response.json(data);
}

After (ShipFast)#

export async function GET(req: Request) {
  const session = await getServerSession(authOptions);

  const data = await prisma.project.findMany({
    where: { userId: session.user.id },
  });
  return Response.json(data);
}

Step 5: Update Payment Webhooks#

Move subscriptions from organization to user level:

// ShipFast webhook handler
export async function POST(req: Request) {
  const event = stripe.webhooks.constructEvent(...);

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object;
      // Tie directly to user, not organization
      await prisma.subscription.upsert({
        where: { userId: session.metadata.userId },
        update: { status: 'active', ... },
        create: { userId: session.metadata.userId, ... },
      });
      break;
  }
}

Step 6: Remove i18n (If Not Needed)#

Delete translation files and simplify components:

# Remove i18n files
rm -rf locales/
rm -rf src/lib/i18n/

Update components to use plain strings:

// Before (Supastarter)
import { useTranslation } from 'next-intl';

export function Header() {
  const t = useTranslation('common');
  return <h1>{t('welcome')}</h1>;
}

// After (ShipFast)
export function Header() {
  return <h1>Welcome to My SaaS</h1>;
}

Step 7: Migrate Your Business Logic#

Copy your custom code, removing organization references:

# Identify custom files to migrate
ls -la src/app/api/
ls -la src/components/

# For each custom file:
# 1. Copy to new project
# 2. Replace organizationId with userId
# 3. Remove i18n hooks
# 4. Simplify team/role logic

Testing Checklist#

  • User authentication (sign up, sign in, sign out)
  • Password reset flow
  • Subscription checkout
  • Subscription management (cancel, upgrade)
  • Webhook handling
  • All custom features work without org context
  • Email notifications

Common Challenges#

1. Data Belongs to Organizations#

Problem: Your business data is tied to organizationId.

Solution: Migrate data to user ownership:

// Find the owner of each organization and assign data to them
for (const project of projects) {
  const owner = await oldDb.membership.findFirst({
    where: { organizationId: project.organizationId, role: 'OWNER' },
  });
  await newDb.project.create({
    data: {
      ...project,
      userId: owner.userId, // Replace org with user
    },
  });
}

2. Multi-User Organizations#

Problem: Some organizations have multiple users.

Solution: Either pick the owner, or create separate copies:

// Option A: Assign to owner only
// Option B: Duplicate data for each member (if applicable)

3. Role-Based Features#

Problem: You have admin-only features based on organization roles.

Solution: Simplify to user-level checks or remove:

// Before: Check org role
if (session.user.role === 'ADMIN') { ... }

// After: Check user property or simplify
if (session.user.isAdmin) { ... }
// Or just remove if not needed

Timeline Estimate#

PhaseEstimated Time
Setup & Planning1 hour
Database Migration2 hours
Auth Simplification1 hour
API Route Updates2 hours
Testing2 hours
Total~8 hours

Conclusion#

Migrating from Supastarter to ShipFast is relatively straightforward since you're removing complexity rather than adding it. The main work is flattening your data model from organization-based to user-based ownership.

This migration makes sense if you've realized the extra features of Supastarter are adding maintenance overhead without providing value for your specific use case.

#nextjs#simplification#payments

Not sure which boilerplate to choose?

Take our 2-minute quiz and get personalized recommendations.

Take the Quiz
Migrating from Supastarter to ShipFast | MyStarterStack