Background
ReviewKit had real customers and real revenue. The product worked. But Nina had started keeping a private list of things she was worried about in the codebase — places where a change in one part would break something in another, inconsistencies in how the auth state was checked, API routes that had been written at different times by different mental models. The list was getting longer. Neither she nor her co-founder wanted to do a rewrite, but the alternative — continuing to build on a shaky foundation — was getting more expensive with every feature.
The challenge
The decision to migrate was uncomfortable for a few reasons. The product was in the hands of paying customers. A migration sprint meant a sprint with no new features. And there was a real risk that moving to a new foundation would surface problems that would take longer to fix than the migration was supposed to take. Nina made the call anyway. She set a hard rule: one sprint, ship nothing visible, stabilize everything internal.
How they built it
Migration sprint: one week, no new features
Nina and her co-founder spent the sprint migrating ReviewKit's infrastructure layer onto ShipAI's foundation. Auth moved to a consistent implementation. API routes adopted a uniform error handling pattern. The database access was standardized on Drizzle ORM throughout. The migration surfaced six bugs — places where the old codebase had inconsistencies that the new patterns made impossible. All six were fixed before the sprint ended.
The bugs that were hiding
One of the six bugs was in the session invalidation logic — a case where a user's session wouldn't be correctly invalidated after an account email change. It had never manifested because no customer had changed their email. It would have manifested eventually. Finding it during the migration, before it reached a customer, was exactly the kind of outcome Nina had hoped for but hadn't counted on.
Code review changed after the migration
Before the migration, reviewing a new API route meant reading the whole thing carefully because the structure varied. After the migration, every API route followed the same pattern: validate input, check auth, execute business logic, handle errors. Reviewing a new route meant checking whether the business logic was correct — the structure was assumed. Review time dropped from 30–40 minutes per route to 10–15.
Two months of the most stable shipping they'd had
In the two months following the migration, ReviewKit's QA pass rate went from 70% to 91%. Not because they'd gotten better at writing tests — because the code was more predictable. Regressions got harder to introduce when every layer followed the same patterns. The private list Nina had been keeping stopped growing.
Outcomes
QA pass rate 70% → 91%
First-review QA pass rate improved by 21 percentage points in the two months following the migration — the best sustained quality period the team had had.
6 latent bugs found and fixed during migration
Including one in session invalidation that would have been a customer-facing security issue if it had reached production.
Code review time dropped by more than half
Average PR review time went from 30–40 minutes to 10–15 minutes because the structure was predictable and reviewers focused on logic, not pattern.
No architectural debates in four months
Since the migration, the team has had zero discussions about where new code belongs. The structure makes the answer obvious.
In their own words
We spent a sprint doing nothing visible. No features shipped, no changelog entries, nothing to show customers. That was the right call. In the months after, we moved faster and broke fewer things than we had since the early days. The sprint cost felt real in the moment. The payoff has been larger and longer-lasting than I expected.
“We were shipping fast but breaking things just as fast. Every new feature felt like a gamble. Migrating to ShipAI was uncomfortable — it took a sprint just to move — but after that the codebase started feeling predictable again. That's worth a lot when you're a team of two.”
— Nina Kozlova
Frequently asked questions
How did Nina manage customer expectations during the migration sprint?
She didn't communicate it externally. From a customer perspective, nothing changed — no features were released, but nothing broke. The migration happened entirely in the infrastructure layer, invisibly.
What was the hardest part of the migration?
The database layer. Moving from mixed raw SQL and ORM access to Drizzle ORM exclusively required touching every data access call. The auth and billing migrations were faster because the patterns were clearer.
Would Nina recommend migrating an existing product to ShipAI?
Yes, with caveats. The migration is real work — one sprint is about right for a product of ReviewKit's scope. She'd only recommend it if the existing codebase has real consistency problems. If the foundation is already clean, there's less to gain.