Micro frontends are overkill. Use static analysis instead.
TL;DR
If the only problem you're solving is "multiple teams working in the same codebase without stepping on each other," you don't need micro frontends. An ESLint plugin that enforces module boundaries at lint time gives you the same isolation with a fraction of the complexity.
At a previous company, I worked on the platform team. We had a micro frontend architecture: hundreds of packages, separate build pipelines, shared dependency management, managing tooling for each package: it becomes unwieldy with time.
The architecture solved one problem well: teams could work on their features without accidentally coupling to another team's code. Feature A couldn't import from Feature B because they were separate packages with separate builds.
But the cost of that isolation was enormous. Shared dependencies needed careful version coordination. Build times ballooned. Routing between micro frontends required glue code. A new developer joining the team had to understand the entire orchestration layer before they could be productive. The architecture existed to enforce boundaries, and everything else was overhead.
What boundaries actually need
Most teams adopting micro frontends never use deployment independence or runtime isolation. The core requirement is simpler: prevent Feature A from importing Feature B's internals.
That's a static analysis problem, not an architecture problem.
eslint-plugin-project-structure
On a later project, where I had full control over the architecture, I reached for eslint-plugin-project-structure instead. It's an ESLint plugin with three rules: folder-structure for enforcing directory conventions, independent-modules for controlling what each module can import, and file-composition for governing the internal structure of files. The independent-modules rule is the one that replaces what micro frontends gave us.
You define modules as glob patterns over your source tree and declare what each module is allowed to import:
modules: [
{
name: "Feature modules",
pattern: "features/**",
allowImportsFrom: [
"{family}/**", // can import within the same feature
"shared/**", // can import from shared utilities
],
},
{
name: "Shared utilities",
pattern: "shared/**",
allowImportsFrom: [
"{family}/**",
],
},
]
The {family} keyword resolves to the deepest common path between the importing and imported file. Files in features/checkout/ can import from each other but not from features/search/. You don't enumerate every feature by name; the pattern handles it.
If a developer in features/checkout/ tries to import from features/search/utils, ESLint flags it. In the editor, in CI, everywhere you run lint.
The result is enforced boundaries without separate packages. One repo, one build, one deployment. No module federation, no shared dependency orchestration, no routing glue between micro apps. The other two rules handle conventions that code review catches inconsistently: file naming, test co-location, export structure. And because it's all ESLint config, adoption is incremental. Define modules for one feature folder, get the team comfortable, expand.
The tradeoff
What static analysis does not give you: independent deployment, runtime isolation, or the ability to use different frameworks in different parts of the app. These are the problems micro frontends actually solve, and they're niche. Most frontend teams don't need to deploy features on independent release cycles or run React and Angular side by side. Most teams adopted micro frontends because "that's how you scale frontend." They ended up maintaining hundreds of packages to solve a problem that a config file could handle.
Result
The entire module boundary setup is a single ESLint config. New developers understand it in minutes. Build times are what you'd expect from a monolith (fast). Teams stay out of each other's code because the tooling enforces it, not because the code lives in separate packages.
If your micro frontend architecture exists to enforce boundaries, you've over-engineered the problem. Make the boundaries a lint rule and ship a monolith.