Blog / Web Dev

Your Module System is an Architecture Decision (And You're Probably Making it Too Late)

Most JavaScript projects fail not because of bad code, but because nobody decided how code should live together.

Juan David Avellaneda April 16, 2026 4 min read 6 views
Your Module System is an Architecture Decision (And You're Probably Making it Too Late)

The moment you stop thinking about modules

I built a real-time dashboard for a logistics company in 2022. Forty thousand lines of JavaScript across six months. Nobody died, but the codebase did—slowly, then all at once. The problem wasn't React or TypeScript or even the API layer. It was that we'd never actually decided how our code should organize itself. Each developer had their own mental model. Some nested folders five levels deep. Others threw everything in a utils folder and called it a day. I'm not sure this is the right move, but I think most teams skip the module architecture conversation because it feels premature, like you're optimizing before you've even built anything.

You're wrong about that timing. Here's the thing: a module system is not infrastructure. It's a contract. It's how your team agrees that code should talk to code, and that agreement either happens deliberately or it happens by accident through sheer technical debt accumulation.

What I learned from shipping too fast

  • Naming conventions matter more than you think—especially when six people are making different decisions about what a "service" is versus a "hook"
  • A single shared folder
  • Circular dependencies will destroy your confidence in the codebase before they destroy the build, and psychological damage is harder to fix than import cycles
  • ESM versus CommonJS is a real conversation you need to have written down somewhere, not debated in PR comments at 11 PM

I watched a team at a fintech startup in Medellín implement a strict barrel pattern for exports—every module had an index.ts that controlled what was public. Sounds rigid. It saved them eight hours a week in debugging because the API surface was predictable. There was still chaos elsewhere, but at least you knew what you were importing from where.

The opposite approach works too, depending on your temperament. Some teams thrive with a flat structure where everything is visible and there's radical transparency about internal state. This requires discipline and trust. I'm not sure this actually works at scale, but I've seen it function beautifully in teams smaller than eight engineers.

Where most projects fracture

The real damage happens in that middle phase. Not when you're building the prototype. Not when you're at three hundred thousand lines of code. It's that awkward stretch where you have maybe thirty thousand lines, the team doubled last quarter, and suddenly code that made perfect sense to you is completely opaque to the person who just started. That's when your module system either saves you or becomes your archeological nightmare.

I see this in every migration I touch. Legacy projects that desperately need refactoring but the refactoring itself requires understanding seventeen different folder structures because nobody documented why components/ui is separate from components/forms, which is separate from features/auth/components. The code still works. Users don't care. Your team slowly dies inside.

There's a version of this where you over-engineer on day one and spend six months reorganizing because you didn't actually know what your application would become.

How I actually make the decision now

I ask three questions before writing the first src/index.ts:

  • How will a junior developer find the code they need to change without asking me?
  • Can we test this module in isolation, and does the answer matter for what we're building?
  • Will this structure still make sense if the codebase triples in size?

Not revolutionary questions. But the answers should be written down. In a document. That someone besides you can read six months from now.

The real architecture decision isn't about folders. It's about visibility. What's public API and what's internal? Where does business logic live versus UI logic? Should your components know about your data fetching, or should there be a layer between them? How do you handle shared state that three modules need but that nobody should modify directly?

These aren't questions with perfect answers. They're questions with consequences that compound.

The honest part

I still get it wrong sometimes. I built a design system in 2023 that I'd restructure completely if I started today. The principles I locked in are actually pretty solid, but the implementation is baroque in places. And I can't retroactively change it without breaking twelve projects that depend on the current shape.

So you make your best guess. You document it. You leave room to evolve. And you accept that the module system you design today is a temporary solution that'll need rethinking eventually.

#JavaScript #architecture #modules #project-structure #scalability

Was this helpful?

Juan David Avellaneda

Juan David Avellaneda

Innovation Specialist · Bogotá, Colombia