
Migrating from ShipFast to Supastarter
Upgrade your ShipFast project to Supastarter for better i18n support, multiple payment providers, and enhanced team management features.
Prerequisites
- •Familiarity with Next.js App Router
- •Understanding of your current ShipFast codebase
- •Basic knowledge of Prisma/database migrations
Migrating from ShipFast to Supastarter
If you've outgrown ShipFast and need features like internationalization, multiple payment providers, or more robust team management, Supastarter is a natural upgrade path. This guide walks you through the migration process.
Why Migrate?#
You might want to migrate if you need:
- Built-in internationalization (i18n) support
- Multiple payment provider options (Stripe, Lemon Squeezy, Paddle)
- More sophisticated organization/team structures
- Enhanced role-based access control
Stay with ShipFast if:
- You're building a simple SaaS without i18n needs
- Single payment provider (Stripe) is sufficient
- You don't need complex team hierarchies
Migration Overview#
| Aspect | ShipFast | Supastarter |
|---|---|---|
| Auth | NextAuth with limited providers | NextAuth with extensive providers |
| Payments | Stripe/Lemon Squeezy | Stripe, Lemon Squeezy, Paddle |
| Database | Prisma + various DBs | Prisma + Supabase/Postgres |
| i18n | Not built-in | First-class support |
| Teams | Basic or none | Full organization model |
Step 1: Set Up a Fresh Supastarter Project#
Don't try to retrofit Supastarter into your ShipFast project. Instead, create a new Supastarter instance and migrate your code:
# Clone or download Supastarter to a new directory
git clone [your-supastarter-repo] my-app-v2
cd my-app-v2
npm install
Step 2: Migrate Your Database Schema#
Export Your Current Data#
# From your ShipFast project
npx prisma db pull
pg_dump -U postgres your_database > backup.sql
Map Schema Differences#
ShipFast typically uses a simpler user model. Supastarter adds organizations:
// ShipFast User (simplified)
model User {
id String @id @default(cuid())
email String @unique
name String?
// ... basic fields
}
// Supastarter adds Organization layer
model User {
id String @id @default(cuid())
email String @unique
name String?
memberships Membership[]
}
model Organization {
id String @id @default(cuid())
name String
memberships Membership[]
}
model Membership {
id String @id @default(cuid())
userId String
organizationId String
role Role
user User @relation(...)
organization Organization @relation(...)
}
Create a Migration Script#
// scripts/migrate-users.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() {
const users = await oldDb.user.findMany();
for (const user of users) {
// Create user in new schema
const newUser = await newDb.user.create({
data: {
id: user.id, // Preserve IDs for foreign key relationships
email: user.email,
name: user.name,
emailVerified: user.emailVerified,
},
});
// Create a personal organization for each user
await newDb.organization.create({
data: {
name: `${user.name || user.email}'s Workspace`,
memberships: {
create: {
userId: newUser.id,
role: 'OWNER',
},
},
},
});
}
console.log(`Migrated ${users.length} users`);
}
migrateUsers();
Step 3: Migrate Authentication#
Both use NextAuth, but configurations differ:
ShipFast Auth Config#
// ShipFast typically has:
export const authOptions = {
providers: [
GoogleProvider({...}),
EmailProvider({...}),
],
// Basic callbacks
};
Supastarter Auth Config#
// Supastarter adds organization context:
export const authOptions = {
providers: [
GoogleProvider({...}),
EmailProvider({...}),
GitHubProvider({...}),
// More providers available
],
callbacks: {
async session({ session, user }) {
// Supastarter adds org context to session
const membership = await getActiveOrganization(user.id);
session.user.organizationId = membership?.organizationId;
session.user.role = membership?.role;
return session;
},
},
};
Migration Steps#
- Copy your OAuth credentials to the new
.env - Update callback URLs in Google/GitHub consoles
- Test authentication flow in the new app
Step 4: Migrate Payment Integration#
If Using Stripe#
Both support Stripe, but Supastarter's implementation may differ:
// ShipFast webhook handler
export async function POST(req: Request) {
const event = stripe.webhooks.constructEvent(...);
switch (event.type) {
case 'checkout.session.completed':
await handleCheckout(event.data.object);
break;
// ShipFast-specific handling
}
}
// Supastarter webhook handler
export async function POST(req: Request) {
const event = stripe.webhooks.constructEvent(...);
switch (event.type) {
case 'checkout.session.completed':
// Supastarter ties subscriptions to organizations
await handleOrganizationCheckout(event.data.object);
break;
}
}
Key Differences#
- Subscription owner: ShipFast ties to User, Supastarter to Organization
- Webhook events: Update your Stripe webhook URL
- Price IDs: You can keep the same Stripe products
Migration Script for Subscriptions#
// scripts/migrate-subscriptions.ts
async function migrateSubscriptions() {
const subscriptions = await oldDb.subscription.findMany({
include: { user: true },
});
for (const sub of subscriptions) {
// Find user's primary organization
const membership = await newDb.membership.findFirst({
where: { userId: sub.userId, role: 'OWNER' },
});
if (membership) {
await newDb.subscription.create({
data: {
organizationId: membership.organizationId,
stripeCustomerId: sub.stripeCustomerId,
stripePriceId: sub.stripePriceId,
status: sub.status,
currentPeriodEnd: sub.currentPeriodEnd,
},
});
}
}
}
Step 5: Migrate Your Business Logic#
Move API Routes#
# Identify your custom API routes
ls -la src/app/api/
# Common structure mapping:
# ShipFast: src/app/api/user/route.ts
# Supastarter: src/app/api/user/route.ts (similar, but add org context)
Update API Handlers#
// ShipFast pattern
export async function GET(req: Request) {
const session = await getServerSession(authOptions);
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
return Response.json(user);
}
// Supastarter pattern - add organization context
export async function GET(req: Request) {
const session = await getServerSession(authOptions);
const { organizationId } = session.user;
const data = await prisma.someModel.findMany({
where: {
organizationId, // Scope to organization
},
});
return Response.json(data);
}
Step 6: Migrate UI Components#
Supastarter uses similar Tailwind/Radix patterns, but component APIs may differ:
Button Components#
// ShipFast
import { Button } from '@/components/Button';
<Button variant="primary">Click me</Button>
// Supastarter
import { Button } from '@/components/ui/button';
<Button variant="default">Click me</Button>
Create a Component Mapping#
| ShipFast | Supastarter |
|---|---|
variant="primary" | variant="default" |
variant="secondary" | variant="secondary" |
variant="danger" | variant="destructive" |
Step 7: Add i18n to Your Content#
Supastarter's i18n support lets you internationalize:
// Create translation files
// locales/en/common.json
{
"welcome": "Welcome to {{appName}}",
"dashboard": "Dashboard"
}
// locales/de/common.json
{
"welcome": "Willkommen bei {{appName}}",
"dashboard": "Übersicht"
}
// Use in components
import { useTranslation } from 'next-intl';
export function Header() {
const t = useTranslation('common');
return <h1>{t('welcome', { appName: 'My SaaS' })}</h1>;
}
Step 8: Test Everything#
Critical Test Checklist#
- User authentication flow
- OAuth providers (Google, GitHub, etc.)
- Email verification
- Password reset
- Subscription checkout
- Webhook handling
- Team invitations
- Role-based access
- Data from old database displays correctly
Testing Script#
# Run the test suite
npm run test
# Check auth flows manually
npm run dev
# Visit /auth/signin, /auth/signup, /dashboard
Common Challenges#
1. Session Structure Changes#
Problem: Your code expects session.user.id but Supastarter adds more fields.
Solution: Update all session access patterns:
// Before (ShipFast)
const userId = session.user.id;
// After (Supastarter)
const { id: userId, organizationId, role } = session.user;
2. Database Foreign Keys#
Problem: Your old data references don't match the new schema.
Solution: Preserve IDs during migration and create new relationships:
// Preserve the original user ID
await newDb.user.create({
data: {
id: oldUser.id, // Keep the same ID
// ... other fields
},
});
3. Payment Provider Mismatch#
Problem: Supastarter expects organization-level billing.
Solution: Create an organization for each user and migrate subscriptions to organizations.
Rollback Plan#
If migration fails:
- Keep your ShipFast deployment running during migration
- Use a staging environment for Supastarter testing
- Point DNS to new deployment only after verification
- Keep database backup for 30 days post-migration
Timeline Estimate#
| Phase | Estimated Time |
|---|---|
| Setup & Planning | 2 hours |
| Database Migration | 4 hours |
| Auth Migration | 2 hours |
| Payment Migration | 3 hours |
| UI/Component Migration | 3 hours |
| Testing & Fixes | 2 hours |
| Total | ~16 hours |
Conclusion#
Migrating from ShipFast to Supastarter is a medium-complexity task that's well worth it if you need i18n, multiple payment providers, or robust team features. The key is methodical migration of your database, followed by updating your code to use organization-scoped patterns.
Take your time with testing, especially around payment flows, and consider running both systems in parallel during the transition period.
Not sure which boilerplate to choose?
Take our 2-minute quiz and get personalized recommendations.
Take the Quiz