# Migrate a Lovable app from Vite to Next.js and Supabase

> Step-by-step guide to extract a Lovable Vite SPA into Next.js App Router, secure Supabase server-side cookies, and deploy a standalone app.

Canonical: https://adeolaforgehq.com/guides/migrate-lovable-vite-to-nextjs-supabase/
Last updated: 2026-10-25

Published: 2026-10-25 — 8 min read — Service: [Vibe coded to standalone migration](https://adeolaforgehq.com/services/vibe-to-standalone-migration/)

AI app builders like Lovable get prototypes running remarkably fast. Within a few hours of natural language prompting, you have an interactive interface with styled tables, working login forms, and mock workflows.

The friction begins when real paying users arrive. Lovable code exports as a client-side Single Page Application (SPA) powered by Vite and `react-router-dom`. When you need server-side rendering for search engine discovery, cryptographic webhook validation for Stripe, or secure cookie-based session management, a client-only bundle hits a wall.

Migrating your prototype from client-side Vite to Next.js App Router with an independent Supabase project gives you complete code ownership, zero vendor lock-in, and an architecture ready for production traffic.

Here is the technical migration sequence our studio uses to decouple Lovable prototypes into maintainable standalone repositories.

## Step 1: Export the codebase and database backup

Before modifying any source files, separate your code from the managed cloud instance:

1. **GitHub synchronization**: Inside Lovable, connect your GitHub account and synchronize the project to a private repository. Clone this repository locally.
2. **Export the database**: In the Lovable dashboard, open your project stack settings, navigate to Lovable Cloud, and initiate a database export. You will receive a PostgreSQL custom-format archive (`.backup` file) containing your table schemas, triggers, Row-Level Security policies, and user auth records.
3. **Provision an independent Supabase project**: Create a new project in your own Supabase account. Restore the `.backup` file using `pg_restore` via the Supabase CLI:

```bash
supabase db restore --db-url "postgresql://postgres:[YOUR-PASSWORD]@[YOUR-HOST]:5432/postgres" < export.backup
```

Restoring the full dump preserves hashed user passwords, ensuring existing customers do not experience broken login sessions.

## Step 2: Transition from Vite to Next.js App Router

Lovable exports rely on client-side routing packages like `react-router-dom`. In Next.js App Router, routing is determined by the filesystem structure under `app/`:

1. **Initialize the Next.js shell**: Create a Next.js project with TypeScript and Tailwind CSS (`npx create-next-app@latest`). Copy over your custom components from `src/components/` and utility functions from `src/lib/`.
2. **Restructure client views**: Replace `<Routes>` and `<Route path="/dashboard" element={<Dashboard />} />` declarations with page files:
   - `src/pages/Index.tsx` becomes `app/page.tsx`
   - `src/pages/Dashboard.tsx` becomes `app/dashboard/page.tsx`
   - `src/pages/Auth.tsx` becomes `app/(auth)/login/page.tsx`
3. **Replace client links**: Change all `import { Link } from "react-router-dom"` references to `import Link from "next/link"`.
4. **Environment variables**: Update variable prefixes. Vite uses `import.meta.env.VITE_*`, which exposes values to the browser. In Next.js, rename public keys to `NEXT_PUBLIC_*` and keep sensitive backend keys strictly server-side:

```bash
# Public keys (browser accessible)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# Private secrets (server route handlers only)
SUPABASE_SERVICE_ROLE_KEY=your-secret-service-role-key
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
```

## Step 3: Replace client-only auth with server cookies

Lovable prototypes typically initialize Supabase in the browser using `@supabase/supabase-js` and store JWT session tokens in browser `localStorage`. 

`localStorage` has two critical vulnerabilities in production:
- It is readable by any client-side JavaScript, leaving sessions exposed to cross-site scripting (XSS).
- It cannot be read by Next.js Server Components, middleware, or edge functions before page rendering, causing layout shifts and auth redirects.

Migrate to `@supabase/ssr`, which manages authentication through `HttpOnly` server cookies:

```typescript
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Handled in middleware for Server Components
          }
        },
      },
    }
  );
}
```

With cookie-based auth in place, you can enforce route protection directly in `middleware.ts`, preventing unauthenticated visitors from downloading dashboard assets.

## Step 4: Secure payment webhooks and API routes

In Lovable apps, payment flows frequently rely on redirect parameters (e.g. checking `?success=true` in the URL) to grant credits or upgraded user roles. A visitor who inspects the frontend code can navigate directly to the success URL and bypass billing.

Move all subscription mutations into a Next.js API route handler that verifies cryptographic signatures directly with Stripe:

```typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { createClient } from '@supabase/supabase-js';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function POST(req: Request) {
  const body = await req.text();
  const signature = (await headers()).get('stripe-signature');

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature!,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: 'Webhook signature verification failed' }, { status: 400 });
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;
    const userId = session.metadata?.userId;

    // Mutate subscription records using server credentials only
    await supabaseAdmin
      .from('profiles')
      .update({ subscription_tier: 'pro', updated_at: new Date().toISOString() })
      .eq('id', userId);
  }

  return NextResponse.json({ received: true });
}
```

Notice that `SUPABASE_SERVICE_ROLE_KEY` is loaded only inside this server-side route handler. It never ships to the browser bundle.

## Step 5: Preparing for native mobile packaging

Once your web application runs on a clean Next.js architecture with dedicated database tables and server-side authentication, converting it to a native mobile app becomes straightforward.

Using Capacitor, you can wrap your production web views into native iOS and Android packages that share the same backend, database, and auth cookies. Because the underlying code is clean and modular, passing Apple App Store review (and satisfying Guideline 4.2 Minimum Functionality) is predictable. Our studio successfully used this exact path to ship [SlabMath: Predict & Profit](https://apps.apple.com/us/app/slabmath-predict-profit/id6758900601) to the iOS App Store.

## Summary

Migrating from a visual AI builder to a standalone codebase requires five clear steps:
1. Export your source code and extract your PostgreSQL database dump.
2. Port client-side `react-router` views into Next.js App Router.
3. Replace browser `localStorage` auth with `@supabase/ssr` server cookies.
4. Move sensitive mutations and Stripe webhooks into verified backend route handlers.
5. Deploy to independent infrastructure you control.

If your prototype has paying customers and you want an experienced engineer to handle the migration, explore our [vibe coded to standalone migration service](/services/vibe-to-standalone-migration/) or request our [free production diagnosis](/diagnosis/) for written findings on your live app within 48 hours.

## Related

- [Case study: Cuzinz Property Maintenance](https://adeolaforgehq.com/work/cuzinz-property-maintenance/)
