One Portal, Many Teams: How We Orchestrate Microfrontends at Scale



Our corporate clients face this problem regularly. Multiple teams working in parallel, each owning a different slice of the product — yet from the user's perspective, everything needs to feel like one coherent experience.
This tension — team independence versus product cohesion — is one of the most common and costly challenges in scaling frontend delivery. Teams end up waiting on each other for releases, getting blocked by shared build pipelines, or fighting over a monolithic codebase where one change affects everyone.
At HipSquare we work with organisations where this friction is real. This post describes the architecture we put together to address it.
The Problem: Coupling That Slows Teams Down
When all frontend code lives in one place, teams are no longer truly independent. A team that is ready to ship has to wait for another team that is not. A team that introduces a bug can break pages it never touched. A team that wants to adopt a different tool or framework faces a politics problem, not a technical one.
The root cause is coupling — at the build level, at the deployment level, and often at the code level too. The more teams share, the more they block each other.
The goal is to remove that coupling without removing the product coherence users expect.
The Solution: A Shell Portal That Does As Little As Possible
Our approach is built on one principle: the shell portal should have minimum logic. Its only job is to discover what microfrontends exist, load them at the right moment, and give them space to render. It does not own features, does not contain business logic, and does not need to know what a microfrontend does internally.
This keeps the shell stable and predictable. It changes rarely. Teams are never blocked waiting for a shell release.
That constraint — minimum logic in the shell — shaped every decision in the implementation.
Everything else — feature development, testing, deployment — belongs to each microfrontend team.
Framework-Agnostic by Design
One important consequence of this model is that the shell portal is framework-agnostic.
Microfrontends integrate through a standard browser primitive — a custom element. The portal mounts a custom element by name. Whether the team behind that element uses Angular, React, Vue, or plain JavaScript is entirely their choice. The portal does not care.
This matters for organizations with diverse or evolving tech stacks. Teams are not forced into a single framework. New teams can join the platform using tools they already know. And if a team decides to change framework later, the integration contract stays the same.
Team Independence in Practice
In this model, each microfrontend team owns their domain end to end:
- Development: the team works in their own repository, on their own schedule.
- Testing: the team defines their own quality strategy. They are not dependent on a shared test suite.
- Deployment: the team deploys their artifact independently. No coordination with other teams required.
The only thing the shell portal needs from a team is a manifest entry: a URL pointing to their deployed artifact, plus a small amount of metadata. Once that entry is in the manifest, the portal can discover and load the microfrontend automatically.

The Angular MFE deployed and running independently — no portal required.
The Manifest: The Contract Between Portal and Teams
The manifest is a simple JSON file that the portal fetches at startup. It contains the list of all available microfrontends and tells the shell where to find each one.
Because the portal is framework-agnostic, the manifest can describe microfrontends built with completely different technologies side by side:
{
"version": "1.0.0",
"mfes": [
{
"id": "angular-mfe",
"displayName": "Angular MFE",
"route": "angular-mfe",
"type": "module-federation",
"remoteEntry": "https://angular-team.example.com/remoteEntry.js",
"remoteName": "angularMfe",
"exposedModule": "./bootstrap",
"customElement": "angular-mfe"
},
{
"id": "react-mfe",
"displayName": "React MFE",
"route": "react-mfe",
"type": "script",
"scripts": ["https://react-team.example.com/react-mfe.js"],
"customElement": "react-mfe"
},
{
"id": "svelte-mfe",
"displayName": "Svelte MFE",
"route": "svelte-mfe",
"type": "script",
"scripts": ["https://svelte-team.example.com/svelte-mfe.js"],
"customElement": "svelte-mfe"
},
{
"id": "js-mfe",
"displayName": "Vanilla JS MFE",
"route": "js-mfe",
"type": "script",
"scripts": ["https://js-team.example.com/js-mfe.js"],
"customElement": "js-mfe"
}
]
}
Each entry is owned by a different team and built with a different technology. The Angular microfrontend uses module federation for optimized bundle sharing. The React, Svelte, and vanilla JS microfrontends are loaded as plain scripts. From the portal's perspective, the loading strategy is just a field in the manifest — it has no opinion on what is behind it.
The common fields across all entries:
id/displayName— identify and label the microfrontend in the portal.route— defines the URL path where users navigate to it.type— tells the shell how to load it (scriptormodule-federation).customElement— the rendering boundary the portal uses to mount it.
This is the full extent of what the shell needs to know. A team can update, redeploy, or even replace their microfrontend at any time — as long as the custom element is still registered under the same name, the portal does not need to change.
How Microfrontends Communicate
Independent deployment does not mean isolated behavior. Microfrontends often need to coordinate — one action in one part of the product should be visible in another.
We solve this with a lightweight shared event library built on browser-native events. Microfrontends communicate by publishing and subscribing to named events with agreed payload shapes. A team that wants to emit a status update calls publish('statusMessage', { text: '...' }). Any other microfrontend — or the portal — that has subscribed to that event name receives it automatically.
The key properties of this model:
- No direct imports between microfrontends. Teams do not depend on each other's code at runtime.
- Framework-agnostic. Any microfrontend, regardless of how it is built, can publish or subscribe.
- The only shared dependency is the event contract — the event name and the shape of its payload. That is a small and stable surface area to agree on across teams.
Event Log: Making Cross-Team Interactions Visible
Because all communication flows through browser events, it is possible to observe every interaction in one place. The portal includes an event log that captures each published event — what was emitted, which microfrontend sent it, and when.
This serves two purposes:
- Transparency: anyone looking at the portal can see what is happening across microfrontends without digging through browser developer tools.
- Confidence: it makes the distributed nature of the system legible. Teams, product managers, and reviewers can see the runtime behavior directly.
This is not just a debugging aid. It reflects a broader principle: when teams are independent, visibility becomes a first-class concern.

The shell portal orchestrating Angular, React, and Vanilla JS microfrontends simultaneously. The event log on the right captures every cross-MFE interaction in real time.
What This Enables
A new team joins the platform by adding one entry to the manifest — no portal change required. A team that wants to migrate to a different framework updates their own deployment — the portal is unaffected.
The shell stays stable. Feature work happens at the edges, in each microfrontend, independently.
This is a delivery model where team independence and product coherence do not have to be in conflict.
When This Architecture Fits — and When It Does Not
Microfrontends are a good fit when team boundaries are stable, the product has clear domain ownership, and the cost of coordination is genuinely slowing delivery. They are not the right default for every situation.
Worth being aware of before adopting this model:
- Performance overhead. Each distinct framework runtime is downloaded separately. A page with an Angular MFE and a React MFE loads both bundles. Module federation helps when multiple MFEs share a framework, but cross-framework setups pay the full cost. There is also an additional network round-trip for the manifest on every cold load.
- The event contract still needs coordination. The event bus removes runtime coupling, but the shared event types still need to be agreed on, versioned, and published. When a payload shape changes, all consuming teams need to update. It is a small surface area, but it is not zero.
- Local integration testing is harder. To test cross-MFE interactions locally, you need all relevant MFEs running at the same time. Teams working in isolation only discover integration issues when pointing at a live environment.
- The manifest needs an owner. It is a simple JSON file, but on large organisations it becomes a governance question: who has write access, who deploys it, and what is the process for adding or removing an entry.
- CSP configuration. The shell injects script tags at runtime from external origins. Enterprise clients with strict Content Security Policy rules will need infrastructure sign-off before this works.
Scenarios where we would not recommend this approach:
- Small teams with no real handoff boundaries gain little from the independence model and inherit the infrastructure overhead.
- Early-stage products where domain boundaries are still being discovered — slicing into MFEs before the domain is stable makes those boundaries expensive to undo.
- Products with strict performance budgets where bundle size and load time are primary constraints.
See It in Practice
We have built a working reference implementation of this architecture. It includes an Angular microfrontend, a React microfrontend, a vanilla JavaScript microfrontend, the shell portal, the manifest, and the event log — all wired together and independently deployable.
To explore it, start with the manifest to understand the contract, then read the loader service to see how the shell resolves it at runtime. The event bus is a good third stop — it shows how the communication layer stays type-safe and framework-agnostic without coupling teams together.
Questions or thoughts? Feel free to reach out.
Sources
Demo Shell Portal: Try it here: Frontend Orchestration Platform
Deployed MFES: React MFE Angular MFE Vanilla JS MFE
Repository: hipsquare/frontend-orchestration-platform