Migrating from Custom Next.js to ShipFast
medium
20 hours
nextjs
auth
payments
migration

Migrating from Custom Next.js to ShipFast

Stop reinventing the wheel. Move your handcrafted Next.js SaaS to ShipFast for production-ready auth, payments, and email out of the box.

Prerequisites

  • Existing Next.js application
  • Understanding of your current authentication and payment setup
  • Basic knowledge of Prisma or your current ORM

Migrating from Custom Next.js to ShipFast

You've built a Next.js app from scratch and it works. But maintaining custom auth, payment integration, and email sending is taking time away from your core product. ShipFast provides these foundations so you can focus on what makes your app unique.

Why Migrate?#

Migrate to ShipFast if:

  • Auth bugs and edge cases are consuming your time
  • Stripe integration feels fragile or incomplete
  • Email deliverability is a constant headache
  • You want battle-tested patterns for common SaaS features
  • Speed of iteration matters more than custom implementations

Keep your custom setup if:

  • Your auth/payment needs are truly unique
  • You've already invested heavily in robust infrastructure
  • ShipFast's opinions conflict with your architecture
  • You prefer full control over all dependencies

What You Get from ShipFast#

Your Custom CodeShipFast Provides
DIY NextAuth configPre-configured OAuth + Magic Links
Custom Stripe webhooksComplete subscription handling
Email sending logicResend/Postmark integration
Landing pageReady-made marketing templates
Database setupPrisma + migrations ready

Step 1: Inventory Your Current App#

Before migrating, document what you've built:

## My Current App Inventory

### Authentication
- [ ] Email/password login
- [ ] Google OAuth
- [ ] GitHub OAuth
- [ ] Magic link
- [ ] Password reset

### Payments
- [ ] Stripe Checkout
- [ ] Subscription management
- [ ] Webhook handling
- [ ] Customer portal

### Email
- [ ] Transactional emails
- [ ] Marketing emails
- [ ] Email templates

### Custom Features
- [ ] List your business-specific features

Step 2: Set Up Fresh ShipFast Project#

# Clone ShipFast to a new directory
git clone [your-shipfast-repo] my-app-v2
cd my-app-v2

# Install dependencies
npm install

# Set up environment variables
cp .env.example .env.local
# Configure your existing credentials

Critical Environment Variables#

# .env.local

# Database - use your existing database or create new
DATABASE_URL="postgresql://..."

# Auth - your existing OAuth credentials
NEXTAUTH_SECRET="..."
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."

# Stripe - your existing Stripe account
STRIPE_SECRET_KEY="sk_live_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_live_..."

# Email - configure as needed
RESEND_API_KEY="..."

Step 3: Migrate Your Database#

Option A: Fresh Start with Data Import#

// scripts/migrate-data.ts
import { PrismaClient as OldDB } from './old-prisma-client';
import { PrismaClient as NewDB } from '@prisma/client';

async function migrateData() {
  const oldDb = new OldDB();
  const newDb = new NewDB();
  
  // Migrate users
  const users = await oldDb.user.findMany();
  
  for (const user of users) {
    await newDb.user.create({
      data: {
        id: user.id,
        email: user.email,
        name: user.name,
        image: user.avatar,
        emailVerified: user.emailVerified,
        // Map to ShipFast's user fields
      },
    });
  }
  
  // Migrate your custom models
  // ...
}

Option B: Extend ShipFast Schema#

Add your custom models to ShipFast's Prisma schema:

// prisma/schema.prisma

// ShipFast's built-in models
model User {
  // ... ShipFast fields
  
  // Add your custom relations
  projects    Project[]
  workspaces  Workspace[]
}

// Your custom models
model Project {
  id          String   @id @default(cuid())
  name        String
  description String?
  userId      String
  user        User     @relation(fields: [userId], references: [id])
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

model Workspace {
  id        String   @id @default(cuid())
  name      String
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  settings  Json?
  createdAt DateTime @default(now())
}

Step 4: Migrate Authentication#

Replace Your Custom Auth#

// Your custom auth (probably)
// lib/auth.ts
import { getServerSession } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    // Your custom callbacks
  },
};

ShipFast Auth (pre-configured)#

ShipFast already handles this. Just update the environment variables:

# Your existing OAuth credentials work directly
GOOGLE_CLIENT_ID="your-existing-id"
GOOGLE_CLIENT_SECRET="your-existing-secret"

# ShipFast may add more providers easily
GITHUB_CLIENT_ID="..."
GITHUB_CLIENT_SECRET="..."

Update OAuth Callback URLs#

In your OAuth provider consoles, update callback URLs:

# Google Cloud Console
https://your-new-domain.com/api/auth/callback/google

# GitHub OAuth Apps
https://your-new-domain.com/api/auth/callback/github

Step 5: Migrate Payments#

Your Custom Stripe Integration#

// Your custom checkout
export async function POST(req: Request) {
  const { priceId } = await req.json();
  
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
  });
  
  return Response.json({ url: session.url });
}

Use ShipFast's Checkout#

ShipFast provides checkout components. Update your pricing page:

// Before: Your custom checkout button
<button onClick={() => handleCheckout(priceId)}>
  Subscribe
</button>

// After: ShipFast's checkout (check their docs for exact component)
import { CheckoutButton } from '@/components/payment/CheckoutButton';

<CheckoutButton priceId={priceId}>
  Subscribe
</CheckoutButton>

Migrate Existing Stripe Data#

Your existing Stripe customers and subscriptions are in Stripe, not your code. You need to sync them:

// scripts/sync-stripe-customers.ts
import Stripe from 'stripe';
import { prisma } from '@/lib/prisma';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

async function syncStripeCustomers() {
  // Get all users
  const users = await prisma.user.findMany({
    where: { stripeCustomerId: { not: null } },
  });
  
  for (const user of users) {
    // Fetch subscription from Stripe
    const subscriptions = await stripe.subscriptions.list({
      customer: user.stripeCustomerId!,
      status: 'all',
    });
    
    const activeSub = subscriptions.data.find(
      s => s.status === 'active' || s.status === 'trialing'
    );
    
    if (activeSub) {
      // Update user with subscription status
      // ShipFast may store this differently - check their schema
      await prisma.user.update({
        where: { id: user.id },
        data: {
          subscriptionStatus: activeSub.status,
          priceId: activeSub.items.data[0].price.id,
          currentPeriodEnd: new Date(activeSub.current_period_end * 1000),
        },
      });
    }
  }
}

Update Webhook Endpoint#

# Update webhook endpoint in Stripe Dashboard
# Old: https://your-app.com/api/webhooks/stripe
# New: https://your-app.com/api/webhook/stripe (check ShipFast's path)

# Get new webhook secret and update .env
STRIPE_WEBHOOK_SECRET="whsec_..."

Step 6: Migrate Your API Routes#

Move Custom Endpoints#

// Your custom API route
// pages/api/projects/index.ts (or app/api/projects/route.ts)

export async function GET(req: Request) {
  const session = await getServerSession(authOptions);
  
  if (!session) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const projects = await prisma.project.findMany({
    where: { userId: session.user.id },
  });
  
  return Response.json(projects);
}

Adapt to ShipFast Patterns#

ShipFast may have helper utilities. Use them:

// Using ShipFast's auth helpers (example - check their docs)
import { getCurrentUser } from '@/lib/session';

export async function GET(req: Request) {
  const user = await getCurrentUser();
  
  if (!user) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const projects = await prisma.project.findMany({
    where: { userId: user.id },
  });
  
  return Response.json(projects);
}

Step 7: Migrate UI Components#

Inventory Your Components#

src/components/
├── ui/
│   ├── Button.tsx        → Use ShipFast's Button
│   ├── Card.tsx          → Use ShipFast's Card
│   └── Modal.tsx         → Use ShipFast's Modal
├── forms/
│   └── ContactForm.tsx   → Keep or adapt
├── features/
│   └── ProjectList.tsx   → Keep - this is your business logic
└── layout/
    └── Navbar.tsx        → Adapt to ShipFast's layout

Replace with ShipFast Components#

// Before: Your custom button
import { Button } from '@/components/ui/Button';

// After: ShipFast's button (they likely use shadcn/ui)
import { Button } from '@/components/ui/button';

Keep Your Business Components#

Your feature-specific components should migrate with minimal changes:

// components/features/ProjectList.tsx
// This is YOUR business logic - move it as-is

import { Button } from '@/components/ui/button'; // Update imports
import { Card } from '@/components/ui/card';

export function ProjectList({ projects }) {
  return (
    <div className="grid gap-4">
      {projects.map(project => (
        <Card key={project.id}>
          <CardHeader>
            <CardTitle>{project.name}</CardTitle>
          </CardHeader>
          <CardContent>
            {project.description}
          </CardContent>
        </Card>
      ))}
    </div>
  );
}

Step 8: Migrate Pages#

Map Your Routes#

Your RouteShipFast RouteAction
//Use ShipFast's landing or customize
/login/auth/signinUse ShipFast's auth pages
/dashboard/dashboardAdapt your dashboard
/settings/dashboard/settingsMerge with ShipFast's settings
/projects/*/dashboard/projects/*Move your pages

Dashboard Integration#

// app/dashboard/projects/page.tsx
// Move your projects page into ShipFast's dashboard structure

import { getCurrentUser } from '@/lib/session';
import { prisma } from '@/lib/prisma';
import { ProjectList } from '@/components/features/ProjectList';

export default async function ProjectsPage() {
  const user = await getCurrentUser();
  
  if (!user) {
    redirect('/auth/signin');
  }
  
  const projects = await prisma.project.findMany({
    where: { userId: user.id },
    orderBy: { createdAt: 'desc' },
  });
  
  return (
    <div className="container py-8">
      <h1 className="text-2xl font-bold mb-6">Your Projects</h1>
      <ProjectList projects={projects} />
    </div>
  );
}

Step 9: Update Email Sending#

Your Custom Email#

// Your custom email sending
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(email: string, name: string) {
  await resend.emails.send({
    from: 'noreply@yourapp.com',
    to: email,
    subject: 'Welcome!',
    html: `<h1>Welcome, ${name}!</h1>`,
  });
}

ShipFast Email Templates#

ShipFast likely has email templates and helpers:

// Use ShipFast's email utilities (check their docs)
import { sendEmail } from '@/lib/email';
import { WelcomeEmail } from '@/emails/Welcome';

export async function sendWelcomeEmail(email: string, name: string) {
  await sendEmail({
    to: email,
    subject: 'Welcome!',
    react: WelcomeEmail({ name }),
  });
}

Step 10: Test Everything#

Critical Tests#

// __tests__/migration.test.ts

describe('Migration Verification', () => {
  test('users can sign in with existing credentials', async () => {
    // Test OAuth flow
  });
  
  test('existing subscriptions are recognized', async () => {
    // Test subscription status
  });
  
  test('custom features work', async () => {
    // Test your business logic
  });
  
  test('payments process correctly', async () => {
    // Test with Stripe test mode
  });
});

Manual Testing Checklist#

  • Sign in with Google (existing user)
  • Sign up new user
  • Password reset flow
  • Existing subscription recognized
  • New subscription checkout
  • Webhook events processed
  • Dashboard loads correctly
  • Custom features work
  • Email sending works

Common Challenges#

1. Database Field Mismatches#

Problem: Your User model has different fields than ShipFast's.

Solution: Create a migration to map fields:

// Add missing fields to ShipFast schema
model User {
  // ShipFast fields
  id            String @id
  email         String @unique
  
  // Your additional fields
  company       String?
  role          String @default("user")
  bio           String?
  
  // Add any custom fields you need
}

2. Different Authentication Tokens#

Problem: Active sessions become invalid.

Solution: Users will need to re-authenticate. Communicate this:

// Show migration notice
export function MigrationBanner() {
  return (
    <div className="bg-blue-100 p-4">
      We&apos;ve upgraded our app! Please sign in again to continue.
    </div>
  );
}

3. Stripe Customer IDs#

Problem: ShipFast stores Stripe data differently.

Solution: Map during migration:

// Ensure stripeCustomerId is in the right place
await prisma.user.update({
  where: { id: user.id },
  data: {
    stripeCustomerId: existingStripeCustomerId,
  },
});

Timeline Estimate#

PhaseEstimated Time
Inventory & Planning2 hours
Project Setup1 hour
Database Migration4 hours
Auth Migration2 hours
Payment Migration4 hours
Component Migration4 hours
Testing & Fixes3 hours
Total~20 hours

Conclusion#

Migrating from a custom Next.js setup to ShipFast is an investment that pays off in reduced maintenance burden. You get production-tested auth, payments, and email infrastructure, freeing you to focus on the features that make your app unique.

The key is methodical migration: inventory what you have, map it to ShipFast's patterns, and test thoroughly before switching production traffic.

#nextjs#auth#payments#migration

Not sure which boilerplate to choose?

Take our 2-minute quiz and get personalized recommendations.

Take the Quiz
Migrating from Custom Next.js to ShipFast | MyStarterStack