DEV Community

Cover image for From Spaghetti to Scalable: How I Modularized a Growing Frontend Codebase
Arpy Vanyan
Arpy Vanyan

Posted on โ€ข Edited on

22 2 3 4 1

From Spaghetti to Scalable: How I Modularized a Growing Frontend Codebase

Introduction: The "Oh No" Moment

At some point in my career, I took over a large React app that seemed fine at first โ€” until it wasn't. ๐Ÿ˜…

The project had evolved over time, but instead of growing gracefully, it became an unmanageable beast:

โŒ A custom state manager โ€” hard to debug & scale.
โŒ Massive UI components โ€” mixing business logic, API calls, and UI.
โŒ Multiple UI versions for different clients โ€” but all versions lived in the same codebase.
โŒ Releases were painful โ€” changing one part could break something completely unrelated.

At some point, I realized: This is unsustainable.

๐Ÿ‘‰ This is the story of how I refactored that messy frontend into a modular, scalable, and maintainable system without breaking everything along the way.


๐ŸŽฏ TL;DR

  • Messy frontend code? Modularization is the key!

  • Break down your code into components, features, and domains.

  • Keep UI, logic, and data handling separate.

  • NX monorepos solve interdependency issues.

  • Start small, refactor in steps, and enforce structure with tools.


Phase 1: The Giant Monolithic App Era

At the start, our React app was an all-in-one monolith:

  • Custom state management. We had built our own instead of using Redux or anything else (Context API was not that powerful yet).

  • UI + logic bundled together. a single component could be over 500 lines long.

  • Multiple UI versions in one repo. Each client required a slightly different UI, but instead of separating them, all versions coexisted in the same codebase.

  • A single repo handled everything. State, API calls, business logic, and UI, making it hard to maintain and even harder to onboard new developers.

Monolith

Biggest Pain Points:

๐Ÿšจ Scaling was impossible. As we added more clients, the complexity multiplied.
๐Ÿšจ Code duplication was everywhere. Reusing functionality meant lots of copy-pasting.
๐Ÿšจ UI modifications were a nightmare because each client had different design needs, we had tons of if statements handling different layouts.

Lesson Learned:

A single repository containing everything isn't just inefficient โ€” it's a ticking time bomb.


Phase 2: Splitting the Monolith โ€” The First Step to Modularization

Realizing the mess, I decided to refactor the app into separate repositories, each handling a specific concern.

The New Modular Structure:

โœ… A React component library ๐Ÿ“ฆ

  • Contained state, API interactions, and core functionality.

  • Single source of truth for all logic โ€” no more duplication.

โœ… A separate UI module ๐ŸŽจ

  • A pure extension of Bootstrap CSS โ€” no JavaScript, just styling.

  • Clients could now have different layouts without affecting the app logic.

โœ… A React app for pages & UX ๐Ÿ—๏ธ

  • Handled routing, layouts, and page composition.

  • Different apps could now be built using the same API + component library.

Modulized

What Changed?

๐Ÿš€ More flexibility โ€” we could create apps with different layouts without duplicating code.
๐Ÿš€ Easier maintenance โ€” frontend developers could work on UI without touching business logic.
๐Ÿš€ Cleaner separation of concerns โ€” each module had a clear responsibility.

๐Ÿ‘‰ The refactor was a game-changer. But it introduced a new challenge: managing interdependent packages.


Phase 3: The NPM Link Hell & How NX Saved the Day

After splitting the project into multiple repos, we ran into another problem:

โš ๏ธ Local development became painful. We relied on npm linking to work across multiple co-dependent packages.
โš ๏ธ Syncing changes was slow. Any update to one package had to be manually managed in others.
โš ๏ธ Onboarding became complicated. New developers had to manually set up multiple repos just to get started.

I realized that managing separate repos was becoming its own problem.

The Solution: NX Monorepo

To simplify development, we moved all modules into an NX workspace:

โœ… Unified repository structure โ€” All projects lived in one place.
โœ… Built-in dependency graph โ€” NX automatically understood how modules were connected.
โœ… Faster builds โ€” NX only rebuilt the parts of the code that changed.

๐Ÿ‘‰ The migration strategy was key. To avoid disruption, we:

  1. Created the app from scratch in the NX workspace.
  2. Copied the existing React component library without changing logic, just refactored class components into hooks.
  3. Introduced two additional modules to improve the architecture and add features.

NX Diagram

What Changed?

๐Ÿš€ Modular, scalable development โ€” each team could now work on their own package without affecting others.
๐Ÿš€ Faster iteration โ€” no more npm linking headaches!
๐Ÿš€ Better collaboration โ€” dedicated teams worked on separate parts of the frontend.

๐Ÿ‘‰ With NX, we finally had a system that was both modular and manageable.

Next Steps: Refining the NX Architecture ๐Ÿš€

While migrating to an NX monorepo significantly improved our workflow, there's still room for further modularization. The next phase will focus on enhancing maintainability and scalability by:

โœ… Breaking Down Features into Micro Packages
Currently, our React component library contains both core functionality and feature-specific logic. To better align with NX's best practices, we'll:

  • Extract feature-specific modules into standalone NX packages (e.g., @myorg/task-list, @myorg/profile).
  • Ensure each feature package is independent and reusable across multiple applications.
  • Reduce interdependencies between features to simplify development and deployment.

โœ… Introducing Documentation with NX Storybook
To improve*developer experience and collaboration, we'll:

  • Integrate NX Storybook for our UI components and feature modules.
  • Provide interactive documentation where developers can preview and test components.
  • Standardize our design system and API contracts to maintain consistency across projects.

With these improvements, our NX workspace will become even more scalable, well-documented, and developer-friendly โ€” allowing teams to work more efficiently with clearly defined boundaries between features.


Final Takeaways: Lessons I Learned the Hard Way ๐Ÿ˜…

1๏ธโƒฃ Start modularizing early. Don't wait until the project is too big to handle.
2๏ธโƒฃ Separate UI, logic, and state from the beginning. This saves countless hours of refactoring later.
3๏ธโƒฃ Avoid unnecessary complexity. Modularization should make things easier, not harder.
4๏ธโƒฃ Consider a monorepo for interdependent projects. NX solved many problems for us.
5๏ธโƒฃ Migrating gracefully is key. Doing it step by step prevented downtime.


Conclusion: Keep It Simple, Keep It Scalable ๐Ÿš€

Modularization isn't about making things complex โ€” it's about making them manageable.

๐Ÿ’ก If I were to start a frontend project today, I would:

โœ… Use a component-based architecture from day one.
โœ… Decouple UI from business logic immediately.
โœ… Consider a monorepo for interconnected projects.
โœ… Ensure teams have ownership over separate modules.

๐Ÿ”น Have you ever had to refactor a messy frontend codebase?
๐Ÿ”น What's your go-to strategy for keeping your projects modular?

๐Ÿ’ฌ Drop a comment โ€” I'd love to hear your experiences!


๐Ÿ“Œ Want to Learn More?

๐Ÿ”— Here is more of my experience in scaling forntend apps:

๐Ÿ”ฅ Like this post? Share it with your dev friends!

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (8)

Collapse
 
pengeszikra profile image
Peter Vivo โ€ข

Absolute right!

Collapse
 
micaelmi profile image
Micael Miranda Inรกcio โ€ข

Maybe I'll need to do something similiar on a running project at my company, so, thanks for the tips about tools and strategies!
I've never worked with monorepo management before, so I think it'll be quite a challenge. ๐Ÿ˜…

Collapse
 
_arpy profile image
Arpy Vanyan โ€ข

Good luck! And remember not to overcomplicate things ;)

Collapse
 
juristr profile image
Juri Strumpflohner โ€ข

Love it, thank you!

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen โ€ข

Thanks for writing this article!

We would love to invite you to be a This is Learning Contributor and publish it on the This is Learning publication with articles from other This is Learning Contributors in a range of topics, including Nx.

Collapse
 
ananda_padmanabhan_62c4f5 profile image
Ananda Padmanabhan โ€ข

While reading the article I was wondering how API logic can be separated from UI, Why is it going to layout instead of pages, Did you mean layout as components with single responsibility, Then it's fine but all response should be sent to state management.

Collapse
 
tdubs profile image
Tommy Wu โ€ข

Did you ever consider git submodules?

Collapse
 
_arpy profile image
Arpy Vanyan โ€ข

Good question! We actually never considered Git submodules for our project, and hereโ€™s why.

While submodules offer a way to manage separate repositories within a parent repo, they come with their own challenges. We ultimately went with an NX monorepo because it provided a more seamless development workflow for our modular frontend.

Hereโ€™s a quick pros & cons:

โœ… Pros of Git Submodules:

  • Keeps repositories independent โ€“ Each module can have its own history & contributors.
  • Useful when projects are truly separate โ€“ If teams work on completely unrelated parts, submodules can help.
  • Granular access control โ€“ You can give access to only specific repos instead of everything.

โŒ Cons of Git Submodules for us (Why we didn't use them):

  • Versioning headaches โ€“ Each submodule has its own commit history, so keeping everything in sync is a manual process.
  • Complex local development โ€“ Developers need to initialize, pull, and update submodules separately.
  • Harder dependency management โ€“ If multiple submodules depend on the same package, itโ€™s difficult to coordinate updates.
  • Not ideal for tightly integrated projects โ€“ Our frontend app needed shared dependencies and consistent CI/CD, which submodules donโ€™t handle well.

๐ŸŽฏ Why We Chose a Monorepo Instead (NX):

  • Easier dependency sharing โ€“ Every package/module is in the same repo, reducing version conflicts.
  • Faster local development โ€“ No need to manage multiple repo states manually.
  • Consistent CI/CD pipelines โ€“ We can run tests, builds, and deployments across all modules efficiently.
  • Built-in tooling for modularization โ€“ NX understands dependencies and only rebuilds whatโ€™s necessary.

In short: Git submodules can work well for truly independent projects, but for our caseโ€”where modules were tightly coupled and shared dependenciesโ€”a monorepo with NX was the better choice.

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

๐Ÿ‘‹ Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someoneโ€™s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay