Micro Frontend Strategy: Choosing Between Shell Apps and Independent Systems
In our previous posts, we covered how to identify business domains in your monolith and execute the split effectively. Now, let’s tackle a critical decision that many teams face when moving from a monolithic frontend to micro frontends (MFEs): should you use a shell application approach or go with independent systems? Our experience splitting a 200+ page extranet taught us some valuable lessons about ownership, failure modes, and architectural decisions.
The Challenge: From Bottleneck to Distributed Ownership
Our starting point was a large monolithic extranet site owned by a single team of 8–10 engineers, but with contributions from 120+ engineers across other teams. This created a classic bottleneck scenario where the owning team became the limiting factor for everyone else’s productivity.
We had already decided to break the monolith into smaller pieces based on business domains (typically 20 pages out of our 200+ total), use L7 routing at the edge to maintain the user experience of a single system, and transfer ownership of each domain to the product engineering team that worked most in that area.
But when it came to the frontend architecture, we faced a fundamental choice that would shape how our teams operated.
The Two Approaches: Shell vs Independent Systems
We identified two primary architectural patterns for our micro frontend split:
Approach 1: Shell Application with MFE Modules
Create a central “shell” application that acts as the containerEach business domain becomes a micro frontend module within the shellThe shell handles shared components (header, footer, navigation)Module federation manages the loading and integration of different MFEsApproach 2: Independent Systems with Shared MFE Components
Each business domain becomes a completely independent systemShared components like header and footer become their own MFEsTeams mount these shared MFEs in their independent systemsNo central shell application
Why We Chose Independent Systems
After extensive discussion and prototyping, we went with the independent systems approach. Here’s why:
Single Point of Failure Concerns
The shell approach creates a dangerous dependency: one team or system can take down everyone else. If the shell application has issues — whether from a bad deployment, performance problems, or infrastructure failures — every single domain stops working.
With independent systems, each team maintains their own deployment pipeline and infrastructure. If the MFE of the header of footer goes down, the domain systems continue functioning normally, “if” the MFE is mounted defensively that is.
Ownership Culture Alignment
We realized the shell approach fundamentally conflicted with our ownership culture. By creating a shared platform, we were essentially giving teams “platform-as-a-system” — they’d be dependent on another team’s infrastructure and decisions.
Instead, we wanted to provide “tools-as-a-platform.” Each team gets the tools they need (shared MFE components, deployment infrastructure, monitoring) but maintains full ownership and control over their system’s fate.
Defensive Architecture
With independent systems, teams can implement defensive mounting of shared MFEs. If the header component fails to load or crashes, their pages can gracefully degrade, we also introduced a fallback for the header (because navigations is pretty darn important), when each application is deployed, it takes a copy of the current working version of the header MFE at the time and deploys it within its CDN bucket, if the load fails of the current version of the MFE and run time, then it falls back to the tested working version at its deployment time.
This does require some duplication of code, but copy and pasting code isn't always bad, and we were able to library-afy some of it too.
This resilience is impossible with a shell approach where the shell’s failure cascades to everything else.
Implementing the Independent Systems Approach
Here’s how we structured our architecture:
Shared MFE Components
Header and Footer MFE: Contains navigation, user authentication status, global searchCommon UI Library: Design system components as an npm packageOther libraries: As we found other common things we created more packages, example MFE fall back handling, also some libraries dealing with our cross cutting concerns such as translations.
Team Responsibilities
Each domain team became responsible for:
Their independent application with its own deployment pipelineIntegrating and mounting shared MFEs defensivelyMonitoring and maintaining their system’s healthCode reviews and production support for their domainThis allowed us to distribute the ownership responsibilities (workload) among many teams.
L7 Routing Strategy
Our edge routing configuration maps URL patterns to the appropriate independent system:
/orders/* → Orders System /inventory/* → Inventory System /analytics/* → Analytics System /settings/* → Settings System
The Trade-offs We Accepted
No architectural decision is without trade-offs. Here’s what we gave up and how we addressed it:
Increased Complexity
Each team now manages their own build pipeline, deployment, and infrastructure. We mitigated this by:
Providing standardized CI/CD templatesAll systems use internal private cloud, so deployments and infrastructure is standardizedEstablishing common monitoring and alerting patterns, initially with Application Insights, though more recently we are moving to open telemetry
Code and Practice Consistency
Team independence creates a double-edged sword around standards and practices. On one hand, teams can now innovate and experiment without blocking everyone else. On the other hand, this can lead to divergence that makes cross-system contributions more challenging.
The benefits are significant: when we wanted to upgrade our test framework from Jest to Vitest, instead of coordinating a massive monolith-wide change, one team could experiment on their single system. We measured the impact holistically — looking at their deployment frequency, test execution time, and developer feedback — then used that data to prioritize the rollout across other systems.
However, we needed mechanisms to prevent too much divergence:
Shared coding standards and linting configurationsRegular tech talks where teams share their experiments and learningsOptional “office hours” where teams can get guidance on tool choicesDocumentation of approved technology stacks (not mandated, but recommended)We created a paved path using a new project template, the new project template became our idea of what each of the system should look like, it didn't mandated things like “what state management you should use”, but it did have a basic project structure, shared CI pipeline template, BFF in dotnet and clientside using react, both with our standard internal libs, and test runners setup.
Just noting though, there is still some debate as to weather we should mandate what state management we use, personally I don't mind, I just tell my guys to please don’t use redux :)
Lessons Learned
Over 2 years into our MFE implementation, several patterns have emerged:
Start Simple with Shared Components
Don’t over-engineer your shared MFEs initially. Our header started with basic navigation and user info. We’ve incrementally added features like global search and notifications as teams requested them.
Invest in Defensive Mounting
Teams that implemented robust error boundaries and graceful degradation for shared MFEs have had much smoother experiences. Make this a standard pattern from day one.
Communication is Critical
Without a central shell forcing coordination, teams need explicit communication channels. We established regular sync meetings and shared documentation for cross-team dependencies.
Watch out for Conway’s law, teams that are under reporting lines that are more disperse we had more issues with standardizing with, but we could still make it work.
Monitor the Seams
Pay special attention to the integration points between your independent systems and shared MFEs. These are where issues typically surface first.
Avoid too frequent changes to these interfaces, if they change a lot maybe they should be inside the system and the split is wrong.
Looking Forward
Our independent systems approach has delivered on its core promises: increased team autonomy, better failure isolation, and improved development velocity. Teams can move faster because they’re not blocked by a central platform team or worried about breaking other domains. below is a chart showing increase in MR count over time across all the repos.

However, this approach requires discipline and investment in tooling. Teams need mature deployment pipelines, monitoring capabilities, and a strong understanding of defensive programming practices, we had many production issue in the beginning due to not understanding how to defensively program for MFEs.
The choice between shell applications and independent systems isn’t universally right or wrong — it depends on your organization’s culture, team structure, and risk tolerance. For us, prioritizing team ownership and system resilience over simplicity has proven to be the right trade-off.
In our experience, when teams have true ownership — including the ability to make their own technical decisions and control their own destiny — they consistently deliver better outcomes for both the business and their users.
Conclusion
Splitting a monolithic frontend into micro frontends is as much an organizational challenge as it is a technical one. The architecture you choose should reinforce the behaviors and culture you want to see in your engineering teams.
By choosing independent systems over a shell application, we’ve enabled our teams to move faster, fail safely, and take true ownership of their domains. The increased complexity is there, but for organizations ready to invest in distributed ownership, the benefits far outweigh the costs.
The key is understanding your constraints: team maturity, organizational culture as well as structure, and risk tolerance. Choose the approach that best aligns with where you want your engineering organization to go, not just where it is today.