Why migrate?
Lovable is excellent at getting you to a working prototype fast. The moment you need to add billing that doesn't break on edge cases, isolate data between customers, or hand a codebase to a team without months of archaeology — you need a proper foundation.
Already is that foundation. It's not a different stack. It's the same Supabase + Next.js setup Lovable uses, with everything your production app actually needs already wired together.
What carries over without changes
- Your users and sessions. Lovable uses Supabase Auth. Already uses Supabase Auth. Point Already at your existing Supabase project and your users are never touched.
- Your UI components. Both use shadcn/ui and Tailwind CSS. Copy-paste your buttons, forms, and layouts directly into Already's component directory.
- Your database schema. Your Supabase tables stay exactly where they are. You'll add Drizzle schema files that mirror them, but the data doesn't move.
What needs migration work
- Routing. Lovable uses Next.js App Router but tends to use a flat
app/structure. Remap to Already's(app)/(auth)/(public)route groups. - Data fetching. Lovable generates
useEffect-based data fetching in client components. Already uses Server Components. See the before/after below. - Env vars. Lovable uses
VITE_SUPABASE_URLandVITE_SUPABASE_ANON_KEY. Already usesNEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEY. - Auth calls. Lovable calls
supabase.auth.getUser()in components. Already centralizes this withawait requireAuth()inlayout.tsx. - Lovable-specific packages. Remove
lovable-taggerand any@lovable-dev/*packages frompackage.json— these are editor tooling, not application code.
Env var mapping
Rename these in your .env.local — the values stay the same, only the key names change:
# Lovable (remove these)
VITE_SUPABASE_URL=https://xxxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGci...
# Already (add these)
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
Data fetching pattern: before / after
Lovable generates client components with useEffect for data fetching. In Already, prefer Server Components — the data arrives at render time with no loading state, no client bundle weight.
// Lovable pattern — client component with useEffect
'use client'
export default function ProjectList() {
const [projects, setProjects] = useState([])
useEffect(() => {
supabase.from('projects').select('*').then(({ data }) => setProjects(data))
}, [])
return <ul>{projects.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
// Already pattern — Server Component, no useEffect needed
import { db } from '@/lib/db'
import { projects } from '@/db/schema'
import { withOrgScope } from '@/lib/org'
export default async function ProjectList() {
const rows = await withOrgScope(db.select().from(projects))
return <ul>{rows.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
Auth pattern: before / after
// Lovable pattern — getUser() called per component
'use client'
export default function Dashboard() {
const [user, setUser] = useState(null)
useEffect(() => {
supabase.auth.getUser().then(({ data }) => setUser(data.user))
}, [])
if (!user) return <Redirect to="/login" />
return <div>Hello {user.email}</div>
}
// Already pattern — requireAuth() in layout, user passed as prop
// app/(app)/layout.tsx
import { requireAuth } from '@/lib/auth'
export default async function AppLayout({ children }) {
const user = await requireAuth() // redirects to /login if not authed
return <AppShell user={user}>{children}</AppShell>
}
Step-by-step migration
Go to your Supabase dashboard → Project Settings → API. Copy the project URL and anon key. Add them to Already's .env.local using the NEXT_PUBLIC_ prefix (not VITE_). Already will connect to your existing database and users.
Open your Lovable project's package.json and remove lovable-tagger and any @lovable-dev/* packages. These are Lovable editor tooling — they serve no purpose outside the Lovable environment and will cause confusion in your new codebase.
Both use shadcn/ui and Tailwind. Copy your components/ directory contents to Already's components/. Your design tokens and variants paste across without modification.
# From your Lovable project root:
cp -r components/ ../your-already-project/components/
Map your Lovable routes to Already's route groups:
Lovable: app/dashboard/page.tsx
Already: app/(app)/dashboard/page.tsx
Lovable: app/projects/page.tsx
Already: app/(app)/projects/page.tsx
Lovable: app/login/page.tsx
Already: app/(auth)/login/page.tsx (usually already done)
Lovable: app/page.tsx (marketing)
Already: app/(public)/page.tsx
Search your Lovable codebase for useEffect calls that fetch from Supabase. Replace each with a Server Component using Already's Drizzle helpers. The before/after pattern above applies universally. Components that handle user events (clicks, form input) stay as client components with "use client" — only the data-fetching shell moves to the server.
Already uses Drizzle ORM. Add schema definitions in db/schema/ that mirror your existing Supabase tables. Run pnpm db:push to sync — no data migration, just schema declaration.
Already's billing is pre-built. Add your Stripe keys, run pnpm setup:stripe to create products, then wrap any paid route with await requirePlan('pro'). That's it.
The included CLI command automates 70% of this process — env var mapping, route inventory, schema introspection, and a structured TODO report for what needs manual review.
already migrate lovable --source ../my-lovable-app
Common questions
Will my users get logged out?
No. Already connects to your existing Supabase project. Sessions, tokens, and RLS policies are unchanged. Your users will never know anything happened on the backend.
My Lovable app has a src/integrations/supabase/ folder — what do I do?
That folder contains Lovable's auto-generated Supabase client initialization. Copy the credentials out of it (project URL and anon key), but do not copy the client setup code itself. Already's Supabase client lives in lib/supabase/ and is pre-configured for SSR with cookie-based session handling. Using Lovable's client in an SSR context will break auth on server renders.
What if my Lovable app uses Supabase Storage?
Already includes a file upload helper wrapping Supabase Storage. Your existing buckets and files stay intact — just update the upload calls to use Already's lib/storage helper.
How long does a migration take?
A simple Lovable app (5–10 routes, basic auth) typically migrates in 2–4 hours. A more complex app (custom RLS policies, multiple orgs, payment flows) takes a day. The CLI command cuts that in half.