I was handed a SaaS running on Firebase that had to move to Supabase, on a very low budget. This article covers the techniques I used to get it done with an AI harness.
Starting point
The application was a Nuxt.js SaaS with 20 large screens running on Firebase, already written in TypeScript. It worked as a workflow that enriched business data step by step.
Users could manage multiple projects. Each project was owned by one or several “managers”, who could invite “contributors” to go through the workflow. That implied a role system. There was also an admin area.
Data protection requirements in a sensitive business domain, combined with the plan to offer a self-hosted version, pushed the client to replace their backend and database with more open and cost-effective technologies.
The Firebase scope to replace was:
- Auth: basic email/password
- Database: 66 tables with many multi-hop relationships
- Realtime: used to refresh every user’s page on update events
- File storage: used only by an export report feature that never ran in production
Another developer, a Nuxt.js specialist, had estimated this migration at 30 days.
Why it looked undoable
My first look at the project made it clear the codebase was not migration-friendly.
It had started as a tiny SaaS built by one developer on a poor budget, and a lot of shortcuts had been taken along the way to keep the invoices down.
- No ORM, no abstraction layer.
- No tests — this drives every decision in the rest of this article.
- No layering, no architectural effort to split responsibilities.
- Firebase calls everywhere.
- Entities were Firebase-generated objects.
- Business rules and database access mixed together in the Nuxt.js app.
- Some business rules computed on the client side for no real reason (a Monte Carlo simulation, for instance).
On top of that, role and permission checks were scattered across the code. Each one was a potential security issue during the migration:
- Role scope (owners and contributors)
- Multi-tenancy
- Multi-project
- An admin dashboard, reachable directly from the public app, used to configure static data and the app in general
One thing worked in my favor: the app was not in production yet. Most of the data was static configuration data or test data.
Given all of this, the migration looked undoable — or at least far too complicated for the client’s budget without AI.
The wrong approach
At first, I went for a step-by-step migration, the way you would do it by hand:
- Strangler Fig: replace the old system with the new one, piece by piece.
- CDC (Change Data Capture): run both systems in parallel during the migration, then drop the old one.
I quickly saw this was the wrong call. I tried it on a single screen, since a layered approach wasn’t an option. The model stayed inconsistent despite my attempts to steer it, and it confused shared data with tenant-specific data. Records landed in the wrong place, which made me worry about data leaks.
I assumed — correctly, as it turned out — that a more general approach would reduce that risk. A step-by-step migration would probably have worked, but it needed more time to build a solid harness. That was the warning sign, so I went with a big bang migration instead. Probably not the safest option, but on this project money mattered more than risk.
After all, AI is better at code research than I am, and faster. Those are the main skills a migration needs. In theory, it should handle a big bang better than I would.
Big bang, yes — but with an analysis phase
Big bang, yes. But not blindly.
I started with an analysis phase. The model read the code, then asked me functional and technical questions. It produced a large markdown file describing how the migration should be done, and that file became my spec.
From there, I asked it to write a task list into a second markdown file.
Then I launched the migration by handing the model both files: the task list and the analysis.
Tools
State-of-the-art models can pick and combine tools on their own to get through complex tasks.
I leaned on that and let the AI reach for whatever it needed. So I gave it access to:
- Query tools for the source database
- Query tools for the target database
- A local Docker setup to deploy to
- Most importantly, a Chrome browser through MCP — to reach the app, and anything else the model might need
Managing AI laziness
When I ran this migration, Opus was the state of the art. It was great at repetitive, logic-heavy coding tasks, but it tended to be lazy and stop halfway through with a “this is done”.
Tip: when a session claims the work is complete, ask another Claude session to confirm it. This saved me many times. Adding the instruction to the CLAUDE.md prompt file isn’t enough: the context window is still the same, so the laziness and self-congratulation show up anyway.
My technique for reducing AI laziness has been to use as many goals and loops as possible.
Fable has been released since. It’s expensive, but optimized for long-running tasks, so it should reduce the laziness problem.
Letting the loop run
For this job, I built a loop system. It keeps the model working for hours instead of declaring victory early. The idea is simple: give the model a goal, and don’t let it stop until the goal is reached.
Here are some goals I used:
- Migrate this application from Firebase to Supabase by following the steps in
@tasks.mdand the analysis in@analysis.md. - Install this application and the Supabase requirements on my local Kubernetes (k3d) setup.
- List all pages as a checklist in a markdown file. Test each one with Chrome MCP, fix any bugs, and tick the page off. Don’t stop until every page is checked.
- Check access permissions on every Supabase resource, one by one. Write the fixes you suggest into
threat.md. - There is a performance issue on
<screen url>loading. Fix it.
Then the coding agent does its job and tests its own changes through Chrome MCP.
This loop system fixed a lot of screen loading failures. It fixed a wrong auth redirect on its own. And it caught null or incorrect data, by comparing each component of each screen between the old and the new version.
Data migration script
The data migration itself was easy, for the reason I gave earlier: the app wasn’t in production, so there was no real client data, only static data. For the AI, it came down to a copy/paste job into a seeding SQL script.
Result
The migration took 5 days instead of the 30 estimated by the other developer. Deployment took one more day, also with AI assistance.
The app has since gone to production and runs without any issues.