Company: TitanFX LTD Role: Senior Frontend Developer Duration: 8 months (Nov 2024 - June 2025) Team: Solo frontend developer, 4 backend developers
Tech stack: Next.js, React, TypeScript, Storybook, Tailwind CSS, Radix UI, shadcn/ui
A Button That Looked Disabled
Somewhere around month four, a support ticket came in. A user in Southeast Asia couldn't withdraw funds. The button was right there on screen, but it looked disabled. Grayed out. Untouchable. Except it wasn't disabled -- it was a CSS specificity conflict between the old stylesheet and the new component layer, rendering the active state invisible in that particular browser and timezone combination.
On a content site, that's a minor visual bug. On a live trading platform where someone is trying to access their money at 2am, that's a crisis.
I fixed it in twenty minutes. But I sat with the feeling for much longer.
The First Attempt
Rewind to September 2023. We tried to modernize the dashboard. It didn't work.
The codebase was CodeIgniter with jQuery. Stylesheets were a mix of SASS and LESS that nobody fully understood. The UI had grown organically over years, screen by screen, with no shared language for how things should look or behave. We knew it needed to change.
So we started building. And we stalled almost immediately.
The problem wasn't technical. We had the skills to write React components. The problem was that nobody was making UX decisions. Every screen raised questions that nobody had authority to answer. What should the navigation look like? How should forms behave? What's the interaction pattern for a user managing multiple trading accounts? Without dedicated UX leadership, every component became a debate. Progress ground to nothing.
The project quietly died. We went back to maintaining the jQuery codebase.
This is the part that still gets me. We didn't fail because we lacked talent or ambition. We failed because we treated a design problem like an engineering problem. Fourteen months passed before the organization understood the difference.
The Restart
November 2024. We tried again. This time the team restructured. There was proper UX leadership. Clear design direction. Decisions got made.
I was the sole frontend developer. The backend team was four developers, all strong in their domain but trained on jQuery patterns. They'd need to contribute to the new frontend eventually. That constraint shaped everything I built.
But here's the thing. I didn't fully understand that yet. I was thinking about architecture -- Next.js, TypeScript, component composition, all the decisions that felt important in the first week. I wasn't thinking about the backend developers opening a React file for the first time and feeling the floor tilt under them. That realization came later, and it changed the entire project.
The Constraint Landscape
TitanFX is a live trading platform. Thousands of people use it every day to manage real money. Forex traders in Asia logging in at 2am. Account managers running reports. People depositing and withdrawing funds. The dashboard is not a content site you can break for a weekend and fix on Monday. If a page fails, someone can't access their money.
Zero tolerance for breakage. Zero.
And this wasn't a greenfield rewrite where you build the new thing and flip a switch. We couldn't stop the world. The old system had to keep running while the new system grew beside it. Page by page. Feature by feature.
I've heard people describe this as "changing the engine while the plane is flying." I'd call it a live heart transplant. You can't stop the heart. You can't rush. You just have to be methodical and not panic when something bleeds.
Storybook as Survival Mechanism
I didn't adopt Storybook because it's a best practice. I adopted it because I was drowning.
One person building the component layer. Four people who need to consume it. The math doesn't work unless you create a system that lets people help themselves. So every component I built, I documented in Storybook with its props, its variants, its expected behavior. When a backend developer needed to build a page, they opened Storybook and saw exactly what each piece expected. No guessing. No asking me. No meetings about "how does this button work."
This was political as much as technical. I was either going to become the bottleneck that killed the project or make myself replaceable in the best possible way. Storybook made me replaceable. Anyone could look at the component library, understand what was available, and compose pages from it.
My favorite detail from the whole migration: the backend team started catching edge cases I'd missed. Not through code reviews -- through Storybook. They'd browse the visual catalog and ask questions I hadn't thought to ask. "What happens when the account name is 40 characters?" "This dropdown doesn't handle the case where a user has zero trading accounts." The interactive, visual format invited a kind of scrutiny that staring at a pull request never would have.
Here's what I didn't expect. Storybook didn't just solve the knowledge-transfer problem. It changed the team's relationship with the frontend. Backend developers who'd been hesitant to touch React code started contributing pages. Not because they'd learned React deeply, but because the component library made the gap between what they knew and what they needed to know small enough to step across. The migration I thought was a technical project turned out to be a trust-building exercise that happened to involve code.
Running Two Systems
The unglamorous reality of incremental migration is that you maintain two systems. For months.
The old CodeIgniter app served most pages. The new Next.js app served the pages we'd migrated. A routing layer decided which system handled each request. Users didn't know. They'd click a link and maybe they were on the old system, maybe the new one. The experience had to be indistinguishable.
Deciding what to migrate next was never purely technical. The obvious answer is "start with the simplest page." But the simplest page is also the one nobody cares about. We needed to show value early. So we picked pages that were high-traffic and high-pain -- pages where the old system was slowest or where users complained most. That meant taking on more risk upfront. A quiet settings page would have been safer. We went after the account overview instead.
Every page launch followed the same pattern. Build it. Test it. Route 5% of traffic to it. Watch the error logs. Watch support tickets. Wait. Increase to 25%. Wait again. Full rollout. Hold your breath for a week.
Some launches were clean. Some weren't. One page had a date formatting issue that only showed up for users in a timezone we hadn't tested. Another had that CSS conflict I mentioned -- the button that looked disabled when it wasn't. Small things. But on a trading platform, small things have weight.
We fixed them fast. That was the advantage of the new stack. In jQuery, tracking down a rendering bug meant grepping through thousands of lines of procedural code. In React with TypeScript, the component tree told you exactly where to look.
The Numbers, Since You're Wondering
Page loads went from 3.2s to 1.3s. Bundle optimization, code splitting, server-side rendering -- the usual Next.js wins. TypeScript eliminated an entire class of runtime errors that the jQuery codebase produced weekly. New components that used to take hours to build from scratch now took minutes with the shadcn/ui foundation.
But the numbers aren't really the story. They never are.
What I'm Still Sitting With
Eight months in, the migration shipped. The new system was faster, more maintainable, and the team could contribute to it without me hovering over every pull request. By most measures, it worked.
But I keep thinking about the fourteen months between the first attempt and the second. That gap. We tend to narrate failure as setup for eventual success -- "we learned from our mistakes and came back stronger." And sure, that's partially true. But I also wonder how much of the second attempt's success came from the organizational change (real UX leadership) versus anything I did differently as an engineer. If you'd dropped me into the first attempt with all the architectural opinions I have now, would it have mattered? I honestly don't know.
And I think about that button. The one that looked disabled. Twenty-minute fix, months of unease. Because the whole promise of incremental migration is that you're being careful. You're being methodical. You're routing 5% of traffic and watching the logs and holding your breath. And then a CSS conflict slips through anyway, and someone somewhere can't get to their money, and you realize that careful and safe are not the same thing.
You're still just changing the engine while the plane is flying. You're just doing it one bolt at a time and hoping you don't miss one.