The client challenge

A client came to Vibe Unlock with an application they had built using Lovable. Lovable had helped them move quickly from an idea to a working MVP. The platform made many early architectural decisions, including the frontend framework, database, authentication, storage, hosting, preview environments, and AI integration.

That speed had been valuable. The problem appeared when the MVP started becoming a real product.

The application handled personal and health-related information. As the client prepared it for wider use, they reviewed Lovable’s terms, architecture, data flows, infrastructure dependencies, and expected costs at a larger scale.

Lovable’s current terms state that, unless a plan or separate written agreement expressly permits it, customers should not provide protected health information or other sensitive categories of data. The terms also state that its standard services are not intended to provide regulatory-grade safeguards for that data.

Moving to another provider would not automatically make the application compliant or production-ready. The client needed an architecture where they controlled the infrastructure, vendor relationships, access rules, secrets, deployment process, and audit evidence.

The brief was to move the application onto infrastructure the client controlled without rebuilding the frontend or breaking the existing product.

The migration objective

We designed a new architecture using Cloudflare Workers for application hosting, an operator-owned Supabase project for PostgreSQL, authentication, and private file storage, Google Cloud and Gemini for AI-assisted document extraction, and GitHub as the source-controlled build and deployment path.

We kept the existing React and TanStack frontend. The migration focused on taking ownership of the backend, runtime, data flows, deployment pipeline, and security boundaries.

The original architecture

The application had been built with a TanStack Start frontend, Lovable Cloud hosting, a Supabase-compatible database managed through Lovable, managed authentication integrations, private document storage, Lovable’s AI Gateway, and generated build and preview tooling.

The source code was stored in GitHub, but access to the code did not mean the client controlled every part of the running system. We had to identify and migrate:

  • Database infrastructure, records, functions, triggers, and permissions
  • Authentication settings, user identities, and OAuth configuration
  • Private storage objects and metadata
  • Runtime secrets, server functions, and AI request routing
  • Deployment environments, domains, and migration history

The target architecture

Ordinary authenticated database operations continue to use the signed-in user’s Supabase session and JSON Web Token. Those requests remain subject to PostgreSQL Row-Level Security. Server functions that genuinely require elevated access use a separate server-only credential and perform additional authorization and input validation.

  1. BrowserReact routes, the user’s Supabase session, and TanStack server functions
  2. Cloudflare WorkerAuthentication checks, workflows, document processing, and Gemini requests
  3. SupabasePostgreSQL, Row-Level Security, authentication, and private object storage
  4. Google CloudGemini document extraction
The browser never receives a Supabase service-role key or Google API key.

Why this was not a simple database switch

The original backend used Supabase-compatible infrastructure, and the destination was another Supabase project. It would be reasonable to assume the migration was mostly a configuration change. It was not.

A complete migration had to account for schema, records, users, identities, password hashes, private files, policies, grants, functions, triggers, OAuth providers, runtime secrets, AI processing, DNS, deployment, rollback, and verification. Each component required a different procedure.

1. Inventorying the existing system

Before moving anything, we documented every application table, database function and trigger, Row-Level Security policy, authentication provider, storage bucket, server function, environment variable, OAuth callback, external AI request, deployment domain, and migration record.

The inventory showed that the repository’s migration history was not a reliable recipe for reconstructing the source database. Fourteen migration files were absent from its migration ledger even though the corresponding schema changes existed. Two files shared the same version number, and one historical migration deliberately deleted all rows from an observations table.

Replaying every repository migration against imported data could therefore have damaged the restored database.

2. Selecting a safe migration strategy

We rejected the option of creating an empty project, replaying the complete migration chain, and then importing the application data. Instead, we selected a full backup restore followed by an explicit migration baseline.

  1. Export the source PostgreSQL database.
  2. Restore its schema and data into an empty, client-owned Supabase project.
  3. Reconcile the restored schema with the repository.
  4. Repair authentication identities separately.
  5. Transfer private storage objects.
  6. Establish the migration ledger without rerunning historical SQL.
  7. Add forward migrations for corrections and verify the resulting system.

3. Restoring the database

The restored test environment included 26 application tables, five authentication users, six authentication identities, 17 document records, 198 records, and 17 storage metadata records.

We preserved primary keys, foreign keys, timestamps, and relationships. Changing them could have broken household membership, user-to-record relationships, document ownership, consent records, source-document provenance, and storage paths.

Old sessions, refresh tokens, one-time tokens, and session-bound multifactor data were deliberately excluded. Users would authenticate again against the new project rather than carry active sessions into a different signing environment.

4. Repairing authentication identities

Restoring authentication users was not enough. Supabase stores user accounts and login identities separately. During the backup restore, some identity records were processed before their parent users existed and failed.

We built a dedicated repair process that restored durable login identities after the parent users were available. Users retained their account relationships and password hashes, but temporary authentication state did not cross the project boundary.

5. Migrating private files

The PostgreSQL backup contained storage metadata, but not the underlying file contents. We exported the storage objects, preserved their person-based paths, uploaded them to the new private bucket, and downloaded them again for verification.

Verification compared the expected object count, uploaded object count, paths, total bytes, and successful authenticated downloads. A database row claiming that a document exists is not evidence that the file itself migrated successfully.

6. Discovering and repairing database permissions

The database restore intentionally omitted source-specific privileges. The first restored-user login then revealed that authenticated users lacked the table grants needed to use otherwise-correct Row-Level Security policies.

A full audit found that all 26 public tables had RLS enabled, but browser-facing roles had unsafe or incomplete grants. Anonymous users could execute public functions, including some using SECURITY DEFINER. Other roles had inherited privileges such as TRUNCATE, REFERENCES, TRIGGER, and MAINTAIN.

We created a fail-closed ACL repair migration. It rebuilt the intended grants, removed anonymous access, removed authenticated TRUNCATE, allowed authenticated execution of only seven approved functions, and removed unsafe defaults for future tables.

Verified after repair: 26 RLS-enabled tables, 92 expected policies, no anonymous table or function access, no authenticated TRUNCATE, and the required service-role access preserved.

7. Reconciling migration history

Because the database came from a full backup, we repaired the Supabase migration ledger without executing historical SQL again. The repair script accepted only the exact ledger states we had reviewed and stopped if the database did not match the expected baseline.

We also learned that a remote Supabase dry run identifies pending files without fully executing or validating their SQL. A later migration contained a PL/pgSQL syntax error that the dry run did not catch. The revised process now replays the chain in a disposable local database, runs database lint, reviews pending SQL, applies only approved migrations, and verifies the result.

8. Moving the application to Cloudflare Workers

The application used server functions, so deploying only static frontend files was not sufficient. We configured the TanStack and Nitro build for a Worker entry point, static assets, Node.js compatibility, preview deployments, custom domains, environment-specific bindings, and placement near the Supabase project.

Local and staged testing covered production builds, generated Worker configuration, public routes, static assets, authentication, consent, private document access, and consistent Supabase settings across the browser and Worker.

One preview exposed missing Worker-side public Supabase bindings even though the browser was correctly configured. Adding matching SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY bindings fixed it. A successful frontend build had not proven that the full-stack runtime was configured.

Cloudflare documents an official deployment path for TanStack Start applications.

9. Replacing Lovable-managed Google authentication

We replaced Lovable’s authentication adapter with direct Supabase Auth OAuth. That required a Google Cloud OAuth application, the Supabase callback URL, provider credentials, preview and production redirect URLs, consent behavior, existing-user identity continuity, sign-out behavior, and error handling.

Preview environments introduced variable hostnames, so the redirect policy had to support approved Worker Preview URLs without becoming an unrestricted wildcard. The final application no longer depended on Lovable Cloud Auth to exchange Google’s authorization response for a session.

10. Moving AI processing away from Lovable’s gateway

The application already used a Google Gemini model, but requests passed through Lovable’s AI Gateway. We kept Gemini to avoid changing the hosting platform, database, authentication system, AI provider, and extraction behavior at the same time.

The Cloudflare Worker now retrieves the document under the authenticated user’s access rules, prepares the extraction request, calls Google directly, validates the response, and stores proposed results with provenance. The Google credential is restricted to the required API, bound to a dedicated service account, stored as a server-side secret, and never exposed to the browser or committed to GitHub.

AI-extracted values remain proposed until a person reviews them against the original document. That safety control survived the infrastructure change.

11. Moving DNS and deployment ownership

The domain review included nameservers, mail and SPF records, TLS behavior, apex and www routing, redirects, OAuth return URLs, and Worker custom domains.

Feature branches now receive Worker Preview deployments, while production deployment is associated with the main branch. Database migrations remain a separate, explicitly reviewed operation. A code deployment should not silently change production data, and a Worker rollback cannot reverse database or storage writes.

What the migration achieved

The client gained direct control over application hosting, database ownership, authentication, private storage, migrations, access rules, OAuth, server-side secrets, AI request routing, deployments, DNS, and custom domains.

The migration preserved the existing frontend, user relationships, password hashes, login identities, application records, private files, record relationships, source-document provenance, and human review of AI-extracted results.

What production-ready actually means

Moving away from a managed builder does not automatically make an application secure, compliant, or production-ready. It gives the owner control over the work required to reach that state.

Can we demonstrate that the complete system behaves safely where real users and real data will exist?

For this system, that evidence included cross-user access denial, private document isolation, authentication and consent records, OAuth failure handling, secret separation, AI data flows, logging, backup and restore, data retention, incident response, deployment rollback, and final release approval.

What we learned

Migrating a vibe-coded application is rarely just a hosting task. It is a systems-ownership task.

The frontend may already be good enough to keep. The difficult work is usually behind it: reconstructing undocumented architecture, understanding where data flows, preserving users and files, repairing migration history and permissions, replacing platform-specific integrations, separating public configuration from secrets, and producing evidence that the new system works.

If your MVP has become a real product, the architecture underneath it needs to make the same transition.