general
Honest, phase-by-phase guide to migrating a Lovable SPA to Next.js — covering SSR, Supabase SSR auth, SEO, and real time estimates by app size.
Fast path: if you came here from a search for a conversion checklist, start with the Lovable to Next.js conversion playbook. If you need copy-ready AI prompts, use the Lovable.dev to Next.js conversion prompt guide. This article is the deeper engineering playbook: the why, the sequence, and the production traps behind the migration.
Migrating a Lovable app to Next.js is a six-phase engineering project, not a button click. If you are a founder or PM who has hit Lovable's ceiling and is wondering whether the effort is worth it, this post gives you an honest account of what changes, what it costs, and where things go wrong. The short answer: it is almost always doable, it is never trivial, and for most apps it takes one to three months of real engineering time.
Lovable generates React single-page applications built on Vite. Next.js is a full-stack meta-framework with server-side rendering, file-system routing, React Server Components, and a built-in API layer. The surface area difference is real. The good news is that Lovable's output is standard React and TypeScript, so most of your components copy across with minimal changes. The bad news is that routing, data fetching, authentication patterns, and environment variable conventions all need to change. This guide walks you through each phase in the order that minimises pain and avoids regression.
A production Lovable to Next.js migration should pass six checkpoints: inventory the exported app, scaffold the App Router shell, port routes for visual parity, move auth and data boundaries, rebuild the SEO layer, and cut over with redirects and rollback prepared. If any checkpoint is skipped, the project usually fails later as broken sessions, leaked env vars, missing metadata, or split-host indexing.
Before you plan a migration, be honest about whether you actually need one. There are at least three situations where staying on Lovable is the correct engineering decision.
If your product direction is still changing week to week, migrating to Next.js will slow you down significantly. Lovable lets a non-engineer make product changes without engineering involvement. That speed is worth real money in the early stage. Wait until the core flows are stable before spending engineering capital on infrastructure.
The most common reason people migrate is SEO: Lovable outputs a client-rendered SPA, which means Googlebot gets an empty HTML shell and has to execute JavaScript to see your content. For marketing pages this matters. For SaaS products where most content lives behind a login it matters very little. If your growth is word-of-mouth, paid, or outbound, you can stay on Lovable without any SEO penalty worth worrying about.
A Next.js codebase is meaningfully more complex to operate than a Vite SPA. If the engineer doing the migration has not shipped a production Next.js app before, budget two to three times the hours you think it will take. If nobody on the team has that experience, consider a fractional hire to at least anchor the architecture before handing it off. A bad Next.js migration produces a codebase that is harder to maintain than the Lovable app it replaced.
With that caveat in place, here are the five concrete signals that a migration is genuinely warranted.
If you are running a content strategy, a blog, a landing page for every city or use case, or anything where organic search matters, a client-rendered SPA is a structural problem. Google's crawler can execute JavaScript, but it does so inconsistently and with a delay. Server-side rendering or static generation in Next.js guarantees that the crawler sees the same HTML a user does, with no JavaScript required. If you are checking Google Search Console and seeing crawl errors, low coverage, or pages stuck in "Discovered - currently not indexed", the SPA architecture is likely a contributor.
Lovable apps bundle everything for the browser. As your app grows, the JavaScript bundle grows with it, and LCP on a cold load degrades. Next.js lets you split routes, stream server components, and defer client-side JavaScript, which gives you structural tools to fix LCP without heroic optimisation effort. If your PageSpeed Insights score is in the red and the waterfall shows a large JS parse on first load, a framework change is a more durable fix than chasing bundle size.
Supabase Edge Functions are useful but they are not a full backend. If you need webhooks with complex routing, background jobs, PDF generation, custom auth middleware, or third-party API orchestration, Next.js API routes (or Route Handlers in the App Router) give you a proper server context that co-locates with your frontend code. The deployment is simpler and the debugging experience is significantly better than trying to wire everything through Edge Functions.
This is a soft signal but a real one. Senior engineers who interview for a role and see a Lovable-generated Vite SPA with no CI, no type-safe data layer, and Supabase access directly in React components will have concerns. That does not mean the Lovable app is bad - it means a real engineering team expects a real engineering codebase. If hiring is blocked or retention is at risk because of the stack, that is a legitimate business reason to migrate.
Lovable is a small company. Its product direction, pricing, and continued operation are outside your control. Most of its output is standard code you can export, but your development workflow, AI-assisted iteration, and deployment pipeline are all tied to the platform. For early-stage companies this tradeoff is fine. For companies with investors, revenue, and a long-term roadmap, owning a standard codebase on a commodity hosting platform is a lower-risk position.
Before planning the migration, do an honest inventory of what you are keeping, what you are replacing, and what Lovable never gave you in the first place. The table below covers the main areas.
| Area | Lovable SPA | Next.js (App Router) | Migration effort |
|---|---|---|---|
| UI components | React + shadcn/ui + Tailwind | Same - copy across | Low |
| Client-side routing | react-router-dom | File-system (App Router) | Medium - manual port |
| Data fetching | useEffect + supabase-js | Server components + service-role client | Medium to high |
| Authentication | Supabase Auth (client-side) | Supabase SSR (cookie-based sessions) | Medium |
| Database | Supabase (Postgres) | Same Supabase project | None - you keep it |
| SEO / metadata | Limited - SPA only | Full - Metadata API, sitemap.ts, structured data | Low once on Next.js |
| Image optimisation | None built in | next/image with automatic WebP/AVIF | Low - swap tags |
| API routes / backend | Supabase Edge Functions | Route Handlers co-located in /app | Low to medium |
| Payments | Stripe (if added) | Same Stripe integration, webhook handler moves to Route Handler | Low |
| CI/CD | Lovable deploy pipeline | Vercel, Fly.io, or custom GitHub Actions | Medium - new pipeline |
| Team collaboration | Lovable AI editor | Standard git workflow | Process change, not technical |
| Caching / ISR | None | Full - fetch cache, revalidatePath, ISR | Low - additive only |
The honest summary: the UI layer migrates cleanly because Lovable uses standard tooling. The data layer and auth patterns take the most time because they require understanding React Server Components and Supabase's SSR client, which behave differently from the client-side patterns Lovable generates. See our guide to rescuing a Lovable app for a deeper look at the specific issues that show up in Lovable production apps before you commit to a full rewrite.
This is the sequence we use. Do not skip phases or run them in parallel. Each phase de-risks the next.
Before writing a single line of Next.js code, document what you are migrating. This sounds obvious but most failed migrations skip it.
/project/:id).supabase.from( and supabase.functions.invoke( call. Note the table, the operation (select/insert/update/delete), and whether it runs inside a component or in an event handler.VITE_ prefixed variable. These become NEXT_PUBLIC_ in Next.js. Server-only values lose the prefix entirely.The output of Phase 0 is a spreadsheet or doc you will check off as you work through Phases 1-5. Without it you will miss routes and ship a broken redirect on launch day.
Create the Next.js project alongside your existing repo (not replacing it). Keep the Lovable app running in production throughout the migration.
bun create next-app my-app --typescript --tailwind --app
cd my-app
bun add @supabase/supabase-js @supabase/ssr
The critical setup is the Supabase SSR client, which uses cookies instead of localStorage for session management. Create two clients - one for server contexts (Server Components, Route Handlers, Server Actions) and one for the browser.
// 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.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options))
},
},
}
)
}
Install shadcn/ui into the new project using its CLI. Your existing component code will be compatible, but shadcn components are not copy-paste portable as files - reinstall them fresh so the config and dependencies are correct for your Next.js version.
Work through your Phase 0 route inventory one route at a time. Create the corresponding app/ directory structure. For a Lovable route like /dashboard you create app/dashboard/page.tsx. For /project/:id you create app/project/[id]/page.tsx.
Copy the JSX from your Lovable components into the new files. At this stage, keep components as client components ('use client') so you do not have to change data fetching patterns yet. The goal of this phase is visual parity, not architectural improvement. You can refactor to server components in Phase 3.
Replace all react-router-dom imports: Link comes from next/link, useNavigate becomes useRouter from next/navigation, and useParams stays useParams but imports from next/navigation instead. The useSearchParams hook requires a Suspense boundary in App Router - wrap any component that uses it.
This is the most time-consuming phase and the one most likely to introduce bugs. Take it slowly.
The pattern shift: in Lovable, data fetching looks like a useEffect that calls supabase.from('projects').select() inside a client component. In Next.js, the same data can be fetched in a Server Component that runs on the server, with no useEffect, no loading state, and no client JavaScript.
// app/dashboard/page.tsx (Server Component - no 'use client')
import { createClient } from '@/lib/supabase/server'
export default async function DashboardPage() {
const supabase = await createClient()
const { data: projects } = await supabase
.from('projects')
.select('id, name, created_at')
.order('created_at', { ascending: false })
return (
<ul>
{projects?.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
)
}
Not everything belongs in a server component. Any component that uses browser APIs, React hooks, event handlers, or state must remain a client component. The practical split: fetch data in a server component, pass it as props to client components that handle interactivity.
Move Supabase Edge Functions to Route Handlers where it makes sense. A webhook handler at app/api/stripe/webhook/route.ts replaces an Edge Function and is easier to test locally and deploy.
This is the payoff for the migration effort. Next.js gives you the Metadata API, sitemap.ts, robots.ts, and structured data - all rendered server-side and available to crawlers immediately.
Add a generateMetadata export to every page that needs unique title and description tags. For dynamic pages, fetch the data inside generateMetadata - Next.js deduplicates the fetch so you are not hitting the database twice.
Add app/sitemap.ts to generate a dynamic XML sitemap from your database. Add app/robots.ts to set crawl rules. If your content warrants structured data (articles, products, FAQs), add JSON-LD as a server-rendered script tag in each page's layout.
For a deeper look at SEO patterns specific to apps that started on Lovable, see our post on SEO for Lovable apps.
Deploy the Next.js app before you cut DNS. Run both apps in parallel against the same Supabase project for at least a week of testing.
Vercel is the path of least resistance for Next.js deployment - zero config, automatic preview deployments, built-in image optimisation, and Edge Network for static assets. If you need more control or are self-hosting, we use Fly.io for containerised Next.js deployments and Cloudflare Workers with Next-on-Pages for edge-rendered apps. Each has tradeoffs: Vercel is fast to ship, Fly.io is more flexible for background jobs and WebSockets, Cloudflare Workers require careful attention to the Node.js API subset available at the edge.
Update OAuth redirect URIs in Supabase (and any third-party providers like Google or GitHub) before switching DNS. Update Stripe webhook endpoints. Run your full test suite against the new domain on a staging environment before the cutover. Set up 301 redirects for any URLs that change structure.
These are the issues that show up in real migrations that are not covered in the official Next.js or Supabase docs.
In your Lovable app, every Supabase call runs in the browser with the user's session attached automatically via localStorage. In a server component, the session comes from the cookie store. If the cookie is not set or is expired, your server component runs as an unauthenticated request and RLS policies that depend on auth.uid() return no rows silently rather than throwing an error. This means a bug in your auth middleware does not crash the app - it just shows empty content. Always test authenticated server component pages with an expired or missing session cookie before going to production.
If you use the service-role key (for admin operations), RLS is bypassed entirely. This is intentional and sometimes necessary, but if you accidentally use the service-role key in a route that serves user-facing data, you expose data across users. Keep the service-role key strictly server-side and only in contexts where you have validated the user's identity via a separate check.
React hooks (useState, useEffect, useContext, useRef) are not available in server components. The compiler error is clear, but the fix is not always obvious. Components that use hooks must be marked 'use client'. If a third-party library you are using internally calls hooks without exporting client components, you need to wrap it in a client boundary. Check every component you copy from Lovable for hook usage before assuming it can become a server component.
Lovable uses Vite, which exposes browser-safe variables with the VITE_ prefix. Next.js uses NEXT_PUBLIC_. Variables without either prefix are server-only and cannot be accessed in client components. The migration is straightforward but easy to miss: do a global search for import.meta.env.VITE_ and replace each one. Variables that should be server-only (Supabase service-role key, Stripe secret key, any API secret) should lose the prefix entirely - if they currently have VITE_, that means they were shipping to the browser in your Lovable app, which is a security issue worth fixing in the migration.
If you reference images from external domains (Supabase Storage, Cloudinary, a CDN), you must whitelist those domains in next.config.ts under images.remotePatterns. Next.js will refuse to optimise images from unlisted domains and will throw a build error. Pull your image domains from your Phase 0 inventory and add them before you start swapping img tags for next/image.
If your Lovable app is six months old, the shadcn/ui components inside it may be multiple versions behind the current CLI output. When you reinstall shadcn components fresh into your Next.js project, the generated code may differ from what Lovable generated. The differences are usually minor (import paths, variant names) but if you have customised the component internals in Lovable, those changes will not survive the reinstall. Take a diff of each component before copying any customisations across.
These are real ranges based on production migrations, not estimates from people who have not done it. They assume a competent Next.js engineer working full-time on the migration.
Add 20-30% to any estimate if you are also fixing pre-existing issues (RLS gaps, missing error handling, no CI pipeline) that you discover during the Phase 0 inventory. Those are worth fixing in the migration rather than carrying forward, but they have real time costs.
AppHandoff is useful when the Lovable interface is moving faster than the production backend or when multiple people need to coordinate the migration. It tracks route expectations, backend contracts, handoff tickets, and drift between the app that Lovable generated and the system engineers need to maintain.
You do not need AppHandoff for a simple landing page. You do need a handoff layer when the app has roles, billing, integrations, admin workflows, or an external backend team. The cost of migration is not just moving files - it is preserving product intent while the framework changes underneath.
If you want the shortest exact-match checklist, read the Lovable to Next.js conversion playbook. If you want copy-ready prompts for AI agents, use the Lovable.dev to Next.js conversion prompt guide. If your current app is breaking but not ready for a rewrite, start with Rescue Your Lovable App.
// keep reading

general · 13 min
AI developer rates in 2026: £80–£400/hr depending on seniority, geography, and engagement shape. Tables, hidden costs, and honest comparisons.
general · 18 min
Lovable, Bolt, and Cursor compared by someone who has shipped production apps with all three. Honest trade-offs, exact failure modes, and a decision matrix.