The moment this clicked for me, nobody could pull a report.
It was an internal ERP, the system a whole business runs on. Inventory, orders, finance, and the reports people pull out of all of it. On a day like that one, when the reports stall, the whole floor feels it. The people who need the numbers are standing around waiting on a screen that won't load.
The code wasn't broken in some clever new way. It was just old. The app ran on Zend Framework 1, a PHP framework that was about 15 years old by then. It had worked for years. Then the business grew, the load grew with it, and the framework couldn't keep up. There was no knob left to turn inside it.
That project has a name. We call it Polaris, and it's the case study that turned a hunch into two services I now offer. Let me walk through what happened, because the shape of the problem shows up far more often than the specific stack does.
An old framework is a slow-motion outage
Zend 1 didn't fail all at once. It failed the way old frameworks always do, a little at a time.
The packages the app leaned on hadn't shipped an update in years, so every dependency was a dead end. Hiring was brutal, because the developers who knew Zend 1 well had moved on a decade ago. The frontend and the backend lived in one codebase, welded together, so a small UI change could reach into the business logic and break something nobody thought was related. And an unsupported framework meant the security gaps only widened, with no patch coming from upstream.
None of that gets better on its own. It compounds. Features that should have taken days took weeks. Some never got built at all. The report that wouldn't load was just the day it got loud enough that everyone agreed it was a problem.
What we actually did
I rebuilt Polaris on Symfony, and I used the rebuild to fix the structure, not just swap the framework.
The big move was splitting the frontend off from the backend. Once those were separate, we could break the backend into smaller services, each one owning a single part of the system. After that, a change to orders no longer put finance at risk.
None of this was a big-bang cutover. With a larger team and a window of 6 to 12 months, the safe path was to move module by module and run the old and new systems side by side during the handover. The business kept operating the whole time. Pieces crossed over one at a time, and if a piece wasn't ready, the old one was still right there.
The results were the kind you can feel day to day:
- Noticeably faster for everyday work.
- Far fewer production incidents.
- Features shipping in weeks instead of months.
- Lower hosting and maintenance cost.
- Releases going out one piece at a time, with no full-system deploy.
That last one mattered more than I expected. Because the frontend and the services were separate, a change to one part shipped on its own. Nobody had to hold their breath for a monolithic release anymore.
The same problem keeps showing up in newer clothes
Here's the part I want to be precise about. Polaris was not vibe coded. It wasn't generated by an AI tool in an afternoon. It was a serious piece of business software built by people, on a framework that happened to be the right choice 15 years ago and the wrong one now.
But the underlying problem, once you strip away the stack, is exactly the one I keep running into with brand new apps: code that worked yesterday and can't carry tomorrow's scale.
These days the tool doing the building is often Cursor, Claude Code, Lovable, or Bolt. Someone describes an app, an AI writes it, and a week later there's a real product with real users and payments coming through. That's genuinely impressive, and I don't say that to be polite. Getting to paying users is the hard part, and most ideas never make it.
Then the same story plays out, just faster. The app that a tool built quickly starts to strain under load. The database is missing indexes. The N+1 queries an AI left behind pile up. There's no monitoring, so the first sign of trouble is a user telling you. A 15-year-old framework and a same-week AI build are separated by a decade and a half, and they still hit the same wall: the thing that built the app can't carry where the app is going.
Two problems that look alike, two different fixes
Polaris is why I split this into two services instead of one, because the same-looking problem has two very different answers.
Sometimes the stack is fine and only its slow paths need attention. The language and framework can hold, you just have to find what's dragging and fix it. Profiling under real traffic, caching, moving heavy work into background jobs, the missing indexes, connection pooling, and enough monitoring that you see trouble before your users do. That's Vibe Scaler. We work with the code and hosting you already run and harden it underneath you, so you keep shipping to customers while we do it.
Other times the stack itself is the ceiling, the way Zend 1 was. No amount of tuning gets you where the product needs to go. Then you move to a language and framework that can grow, feature for feature, without losing data or the users you already have. That's Vibe Code Migration. It's the Polaris playbook shrunk to fit a smaller app: write down every feature first, port until the new build matches, move the data in stages, and run old and new side by side until the new one has earned the traffic.
The honest answer usually comes down to one question. Is the stack sound and just tired, or has it hit a wall it can't come back from? Scaling in place is cheaper and less risky when it's the right call, so that's where I start. Migration is for when tuning would only buy you patches that won't hold. Most of the value is telling you which one you're actually in before you spend anything on the wrong one.
Why Polaris is the proof and not just a story
I could have written both service pages from first principles. Plenty of people do. I'd rather point at something that already happened.
Polaris was a 15-year-old system that everyone depended on and nobody wanted to touch, and it came out the other side faster, cheaper to run, and shipping features again. The care it took to move a system that size without breaking the business is the same care a smaller app deserves. The scale is different. The method isn't.
If you're sitting on something that works today and you can already feel it not surviving the next round of growth, whether that's an old framework or an app an AI wrote last week, that's the exact pattern these two services are built for. The Polaris case study is the long version of what the move looks like.
Hope you enjoyed this one. If you've got an app that outgrew the tool that built it, or you just want to argue about when to scale versus when to migrate, come find me on X at https://x.com/harundotdev.
On this post
Comments
A reply stays under the note it answers.
No comments yet.
If you have a note on When Your App Outgrows the Tool That Built It, sign in and leave it.