2 min read

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.

bpBhanu Prakash VemulaProduct Tech Lead @ Instaffo
LinkedInX
TL;DR

Strangler 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 PROD

The 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

What made it work

Three things, none of them technical:

  1. Reversibility as a requirement. Every step had a way back, so every step was takeable.
  2. A scoreboard. Uptime, revenue, error rates. Watched continuously, not checked after.
  3. 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.

bp

Bhanu Prakash Vemula

Product Tech Lead, AI Engineering & Frontend Architect. I write about the things I ship — with the numbers attached.