From Monolith to Modular: How I Refactored a React App Without Stopping Shipping
Authored by: Gourav Singla
Rewrites kill products. The graveyard of side projects, startups, and even mature SaaS tools is full of “let me just rewrite it cleanly” promises that bled six months of velocity and never landed. When the React app I maintain as a solo developer outgrew its Create React App foundation, the temptation to scrap the codebase and start fresh on Next.js was real. I didn’t. The app migrated to Next.js 15’s App Router gradually, while user-facing features still shipped every week.
The lessons below come from doing that work, not from theorizing about it. They apply whether the target framework is Next.js, Remix, Vite, or just a cleaner version of what already exists.
1. Extract services before touching the routing layer
The single biggest mistake in any refactor is conflating “the code is organized poorly” with “the framework is wrong.” Most React monoliths aren’t suffering because of the framework. They’re suffering because business logic, UI state, and side effects all live tangled inside components.
Before changing a single route, I pulled logic out of components into single-responsibility service modules. The codebase now has more than 60: a slide manager, a theme registry, an export pipeline, an alignment-guides service, a rate limiter, and so on. Each is plain JavaScript with no framework dependency. None of them know whether they live inside CRA, Next.js, or a Node script.
Once business logic was framework-agnostic, the actual App Router migration became almost mechanical. Pages turned into thin orchestrators that called into the services. The hard work was already done.
Takeaway: Decouple from the framework first. Migrate second.
2. Lazy-load the expensive stuff at the refactor seam
A gradual refactor is also a chance to fix performance debt that nobody had time for in the original implementation. When the code is being touched anyway, that’s the cheapest moment to introduce a cache, defer a load, or split a bundle.
The app ships dozens of visual themes: fonts, color palettes, layout rules, preview thumbnails. In the original CRA version, themes loaded upfront and slowed first paint. During the migration, the theme system was rebuilt around three small services: a lazy loader (load on demand), a preview cache (memoize render output), and a registry (the index everything else queries). The initial bundle dropped. Theme switching stayed instant. Adding the next theme now costs almost nothing.
The point isn’t themes. The point is that a refactor is an excuse to make the next feature cheaper. Pick the modules that hurt most today and rebuild them with the load pattern actually needed in production.
Takeaway: Don’t refactor for symmetry. Refactor for leverage.
3. Use the hardest user flow as the canary
A modular architecture sounds great on a whiteboard but only matters in practice if state survives at the boundaries that matter to users. In any freemium tool with an anonymous-first onboarding, the boundary that matters most is the moment an anonymous user signs up and expects their unsaved work to be there.
That handoff is owned by a single service in the codebase: one that moves a session-scoped draft over to a newly authenticated account without dropping data. Every time the underlying state layer was refactored, this flow was the first thing tested. If it broke, modules were still entangled in ways no architecture diagram could reveal. If it survived, the refactor was real.
Every codebase has an equivalent canary: the user flow where state has to cross a boundary cleanly. Use it as the truth meter for any structural change.
Takeaway: Architecture diagrams lie. The hardest user flow doesn’t.
The real point of refactoring
Modularization is not the goal. Modularization is a tool for reducing the cost of the next change. A monolith that ships features weekly is healthier than a modular cathedral that ships quarterly. The right time to refactor is not when the code feels ugly. It’s when ugliness starts costing days per feature.
Pick the seam that hurts most. Start there. Ship the rest.
About the author: Gourav Singla is a software engineer and independent researcher working on document chunking strategies for LLM content generation.