Decoupling a monolith without breaking revenue
We migrated a revenue-critical frontend off a Scala monolith with 100% uptime and zero revenue disruption. Strangler fig routing, edge rewrites, typed boundaries, test-gated RSC adoption. The actual playbook, including what made it work.
TL;DRStrangler fig done literally: edge rewrites route page by page, typed boundaries wrap legacy assumptions, RSC adoption waits for test coverage, and every step reverses. No cutover day. No freeze. Zero revenue disruption, 100% uptime.
The frontend lived inside a Scala monolith. Revenue ran through it. The mandate: get it out without the revenue noticing.
We did. Zero revenue disruption, 100% uptime through the migration. No big-bang rewrite, no freeze. Here's the playbook.
Strangler fig, but literal
The strangler fig pattern is old advice that most teams still do wrong.
The wrong version: rebuild "the new app" next door, plan a cutover day, pray. The cutover day becomes a week. The week becomes a quarter. Somewhere in there, someone asks why revenue dipped.
The right version: every page moves individually, behind a router that decides who serves what.
- Edge rewrites mapped old URL patterns to the new Next.js app, path by path. Users never saw a "migration domain" because there wasn't one.
- One page at a time. Each moved page was a shippable, reversible unit. Rollback meant flipping a route back.
- The monolith kept serving everything not yet migrated. No freeze, no feature lag.
Typed boundaries for legacy features
Old features leak assumptions: session shapes, globals, render-time side effects. Port the assumptions and you've moved the mess, not the system.
We wrapped each migrated feature behind a typed boundary instead:
- Explicit props and data contracts at the edge
- Legacy behavior isolated, documented, and tested before it moved
- Anything untyped stayed in the monolith until it could be wrapped
The boundary is the migration. Once a feature has a clean contract, moving its rendering is mechanical.
RSC adoption behind test gates
We adopted React Server Components gradually, and the gate was tests, not enthusiasm:
- Pages earned RSC migration only with coverage that could catch render-model regressions
- Hydration mismatches, data-fetching double-fires, and context breaks were the failure modes we gated against
- Each conversion shipped behind the same edge route flag as everything else
FROM PRODThe alternative ("rewrite it as RSC because RSC is the future") is how migration projects get a launch party and then a rollback. Enthusiasm is not a test suite.
What made it work
Three things, none of them technical:
- Reversibility as a requirement. Every step had a way back, so every step was takeable.
- A scoreboard. Uptime, revenue, error rates. Watched continuously, not checked after.
- Smallness. No migration project bigger than one page.
The monolith didn't die dramatically. It shrank until the remaining parts were the parts nobody needed.
That's what a migration with zero revenue disruption looks like: boring, reversible, and page by page. If your migration plan has an exciting weekend in it, the plan is wrong.