How do I move a Lovable app to Next.js?
Plan a reviewed move from Lovable to Next.js while preserving your data, authentication, and payment paths.
Moving a Lovable app to Next.js is a code and provider migration, not a one-click conversion. Preserve a copy of the existing application, inventory its routes and external services, and rebuild or reuse components in a Next.js app while checking each customer journey. Keep the old app available until the replacement is verified.
1. Capture the current system
Connect the project to a GitHub repository using Lovable's GitHub integration, then record the commit you are migrating. Read the repository's package manifest and routing code to identify the actual framework, dependencies, routes, and build commands. Make a list of public pages, authenticated pages, API calls, storage buckets, background functions, and payment events.
Record which provider project owns each database and account. Arrange a tested backup and restoration plan with the provider before changing live data. Do not reset or delete the existing project as a migration step. You should now be able to identify the existing app and its dependencies without relying on the visual editor.
2. Establish a separate Next.js application
Create a Next.js application in a separate directory or branch and run its default page locally. The Next.js Vite migration guide describes a path for Vite apps; inspect your own source first rather than assuming it matches. Its initial client-side approach can preserve an existing router while you migrate gradually.
Next.js App Router uses a root layout and a for each route. Move one page and its dependent components at a time. Components using browser APIs or React state need a client boundary; server-only credentials must never be copied into browser code. Replace Vite environment-variable access with the appropriate Next.js configuration, keeping secrets server-side. Visit each migrated route directly and after a refresh, and compare its behavior with the original.
Connect one exported component
For this example, use a Next.js 16 App Router project with the @/ alias and an existing email/password test user. Complete steps 1–4 of the authentication guide, including its clients, login actions and page, callback, and cookie session-refresh helper. Remove the upstream helper's broad anonymous redirect as directed in that guide; retain its cookie propagation and matcher. The proxy refreshes sessions; it does not authorize access. Point the test setup at the same non-production Supabase project used by the old app. This preserves that project’s user identities, not the browser session across origins; users may need to sign in again.
The guide already supplies a login page. For a sign-in-only example, you can replace it with this page that calls the same action; keep the guide's page if you also need its sign-up form:
import { signIn } from "./actions";
export default async function LoginPage({
searchParams,
}: {
searchParams: Promise<{ result?: string }>;
}) {
const params = await searchParams;
return (
<main>
<h1>Sign in</h1>
{params.result === "error" && <p role="alert">The request could not be completed. Please try again.</p>}
<form action={signIn}>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" required />
<label htmlFor="password">Password</label>
<input id="password" name="password" type="password" required />
<button type="submit">Sign in</button>
</form>
</main>
);
}Password sign-in uses the action directly and does not require a callback. If your existing users sign in through OAuth or another PKCE code flow, configure that provider and the exact new /auth/callback URL in Supabase’s redirect allow list. Use the authentication guide's callback to exchange the code, and configure the OAuth initiation redirectTo to the allow-listed URL using the provider's Next.js OAuth instructions. Complete and test the actual providers your users use; password sign-in alone does not migrate those flows.
Suppose the exported UI includes a presentation component that accepts a name. Copy it into the new project, adapting its imports and styles to match your actual export:
export function Welcome({ email }: { email: string }) {
return <h1>Welcome, {email}</h1>;
}Make the route call the authentication client from the prerequisite guide. This example assumes that guide's createClient is available at @/lib/supabase/server; use your project's actual import if its location differs.
import { redirect } from "next/navigation";
import { createClient } from "@/lib/supabase/server";
import { Welcome } from "./welcome";
export default async function DashboardPage() {
const supabase = await createClient();
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) redirect("/login");
return <Welcome email={user.email ?? "there"} />;
}Here /dashboard calls the server authentication client and passes the verified user's email to the imported component. Use the login route established by your auth setup if it has a different path. Start the application using its documented development command. In a private browser window, /dashboard should redirect to /login; wrong credentials should show an error, and signing in with a test user from that same Supabase project should show that user’s email. Refresh the page, let the session refresh, and repeat with a second user. If using OAuth, follow its provider login through the configured callback and confirm the same account can reach the page. A successful check demonstrates the tested route and identity connection; data and billing authorization need separate checks. Adapt exported interactive components with a client boundary and inspect their dependencies before copying them.
3. Preserve identity and data deliberately
If your existing app uses Supabase, determine whether the replacement will use the same project or a separate project. Reusing the project can preserve its existing users and data, but the new application must use compatible authentication, callback URLs, and database policies. A separate project needs an explicit user and data migration plan; copying table rows alone does not automatically move authentication identities.
Use a non-production project for the first pass. Configure the new origin and callback according to Supabase redirect URL guidance, then test sign-in, sign-out, recovery, and session refresh. Test two accounts against the existing Row Level Security policies: each should see only its own permitted records. Compare representative records and file access before choosing a cutover.
4. Reconnect billing and verify the whole journey
Identify the current checkout creator, webhook endpoint, signing configuration, and stored access state. Build the new server-side path with the same intended product and account mapping. Configure a separate test-mode endpoint and follow a test payment through provider delivery, durable state, and a protected page. Repeat a delivery and test a failed payment; a browser redirect alone must not grant access. The Stripe billing guide explains the full implementation.
Before changing a live endpoint, plan how old and new deployments handle overlapping deliveries and rollback. Preserve customer and subscription identifiers and reconcile any state changes during cutover. Do not point both systems at live events unless their ownership and duplicate handling are understood.
5. Cut over only after verification
Compare the new and old app with the same test cases: public pages, account creation, login and recovery, data isolation, file access, checkout, cancellation, and support workflows. Check logs for errors and keep a rollback route to the old deployment. Schedule the domain and provider redirect changes together, and monitor the first real journeys after switching.
If the goal is a new foundation rather than a route-by-route port, Hikari offers individual accounts and subscriptions; CoreMVP adds shared organizations and persisted projects. Both require you to assess and move your own custom code and data. Neither should be treated as an automatic migration of an existing Lovable application.