Multiple Major Versions of a Framework
Running multiple major versions of a framework in parallel is technically possible. As a permanent target architecture, however, it is usually neither economically nor architecturally sensible.
Without strong isolation, multiple framework runtimes still share the same browser context. With complete isolation, independently running applications are embedded inside one another instead of being composed as seamless microfrontends.
That same isolation can nevertheless be justified as a temporary boundary around an application that remains old for understandable reasons and cannot be migrated safely in one step. It then serves as a migration bridge. It is explicitly not the target state.
Technically Feasible, but Not Sensible as a Permanent State
Section titled “Technically Feasible, but Not Sensible as a Permanent State”A frequently cited promise of microfrontends is that every remote can use its own framework version.
The promise is not false. An integration model can load applications built with different major versions of the same framework. That alone does not create a sustainable architecture. It initially answers only whether the artifacts can somehow be executed together.
It does not answer how many framework runtimes the browser must load and initialize. It says nothing about which global resources those runtimes still share, how compatible versions are resolved, how large the supported test matrix becomes, or how long the version mix is expected to remain. Most importantly, it names no one responsible for bringing the versions back together.
Technical feasibility answers whether something can run. Architecture must also answer whether it should be operated permanently.
Consider a deliberately exaggerated example:
Host└── Angular 22
Remote A└── Angular 17
Remote B└── Angular 19The version numbers are merely placeholders for framework generations that are far apart. The important question is not whether this exact combination works. The important question is what an integration platform must provide when the applications cannot reliably use one shared framework version.
Three Major Versions Are More Than Three Version Numbers
Section titled “Three Major Versions Are More Than Three Version Numbers”When framework dependencies are not provided centrally, each application may have to bring its own runtime in the worst case:
Browser├── Host│ └── Angular 22 Runtime├── Remote A│ └── Angular 17 Runtime└── Remote B └── Angular 19 RuntimeThis is not merely the same package appearing with three version numbers in a lockfile. The browser may receive framework code, bootstrap logic, dependency-injection structures, rendering infrastructure, reactivity or change-detection mechanisms, router and HTTP infrastructure, and framework-specific libraries more than once. Each application also has its own component and application trees, internal data structures, subscriptions, metadata, and lifecycles.
Three major versions can mean three framework runtimes in the worst case.
That does not mean every transferred file or byte is multiplied by exactly three. Tree shaking, lazy loading, browser caching, and differently optimized bundles affect the actual cost. Some technical dependencies may still be shared. A remote may load only when the user opens the corresponding area.
These qualifications do not change the underlying problem. Different runtime versions must be downloaded, parsed, executed, and initialized. They consume memory and create their own internal structures. Their bootstrap work uses main-thread time. Even a bundle already present in the browser cache still has to execute and occupies memory after initialization.
The browser cache can reduce repeated downloads. It does not remove the execution or memory cost of several different runtimes.
The User Pays for Version Freedom
Section titled “The User Pays for Version Freedom”From the teams’ perspective, the situation initially looks convenient:
Team A remains on Major 17Team B uses Major 19Team C migrates to Major 22Each team can decide locally. No shared migration blocks the next product change. The organizational dependency appears to have disappeared.
The same decision looks different in the browser:
User└── loads and runs several framework generationsThe teams avoid part of the shared coordination. The user pays for it through additional runtime cost.
The shift does not end with download size and startup time. Multiple versions increase the number of runtime combinations that platform and product teams must understand and test. They bring different toolchains, library compatibilities, browser assumptions, and maintenance cycles. Failures may appear only in a particular combination of host, remote, and shared infrastructure. Monitoring and support must be able to identify which runtime was involved at which point.
Security and maintenance responsibilities multiply as well. An old version does not remain operable merely because it sits behind a remote boundary. Someone must continue to understand its dependencies, known limitations, build tools, and operational assumptions.
What looks convenient for one team can become expensive for the system as a whole. Independent version choice does not eliminate coordination. It moves part of its cost into the browser, the integration platform, and long-term maintenance.
Shared Dependencies Are a Version Contract
Section titled “Shared Dependencies Are a Version Contract”The obvious alternative is to provide the framework runtime centrally:
Host and Remotes │ └── shared Framework RuntimeA shared runtime can avoid duplicate framework bundles, reduce bootstrap work, and keep framework-specific contracts consistent. Host and remotes then run inside the same runtime environment and can be composed more tightly.
For that to work, they must be compatible with the version that is actually provided.
Shared dependencies reduce runtime cost, but they create a shared version contract.
That contract is not a technical detail. As soon as a framework dependency is treated as a singleton or an equivalently central runtime, a team can no longer choose its major version in complete isolation. A migration must be coordinated, verified, and performed within the agreed range. Updates may affect several applications even when their product releases remain independent.
The alternative is separate runtimes. They offer greater technical version freedom and can enable a gradual migration. In return, runtime overhead, testing effort, and integration complexity increase.
This trade-off cannot be configured away: applications either share a runtime and accept a version contract, or they run separate runtimes and pay their technical and organizational cost.
Separate Runtimes Do Not Mean a Separate Browser
Section titled “Separate Runtimes Do Not Mean a Separate Browser”Separate bundles can easily create the impression that the applications are fully isolated. In a normal composition, however, they still run inside the same document.
They share the same window, the same document, and the same main thread. They access the same browser history and the same global APIs. The DOM, CSS cascade, global styles, CSS custom properties, overlay surfaces under body, localStorage, global error handlers, telemetry instrumentation, and the custom-element registry may all become shared conflict surfaces. Depending on delivery and scope, the same can apply to service workers and global polyfills or patches.
Bundle isolation is not resource isolation. The browser does not know team boundaries.
This does not mean that multiple runtimes must inevitably collide. Well-encapsulated applications can coexist stably for a long time. The greater the version gap and the more global mechanisms are involved, however, the more assumptions must be verified and protected permanently.
A router may try to control the URL and browser history. Global reset styles may affect elements beyond a remote’s visible root. An overlay may render under body, outside the component tree. Global registrations require unique names. Different error handlers and telemetry systems observe the same process. A computationally expensive remote blocks the same main thread as the host.
Separate framework runtimes therefore still compete for shared resources. A runtime boundary does not protect the host from CPU load, memory consumption, or global side effects inside the document.

The Hard Boundary: Complete Isolation in an iframe
Section titled “The Hard Boundary: Complete Isolation in an iframe”When the runtime assumptions can no longer be controlled reliably, the consistent technical boundary is a separate browser context:
Host Application└── iframe └── complete Legacy Application ├── own window ├── own document ├── own Framework Runtime ├── own Router ├── own Styles └── own LifecycleAn iframe separates much more strongly than another bundle in the same document. The embedded application has its own JavaScript globals, DOM, CSS structure, and router infrastructure. Global JavaScript patches or registrations do not automatically affect the host’s window. Framework generations that are far apart can therefore be separated more reliably.
The isolation is real. It is not free.
Navigation and browser history must be coordinated across the document boundary. Authentication and domain context must be passed deliberately. Focus management, keyboard navigation, and accessibility require special attention. Responsive sizing becomes coordination between host and embedded document. Modal dialogs and overlays remain inside the iframe even when the shared user experience would suggest otherwise.
Loading states, failures, and telemetry must also be surfaced across the boundary. Styling and user journeys require additional work to remain coherent. Every necessary interaction becomes an explicit messaging contract.
An iframe buys strong isolation at the cost of expensive integration.
Complete Isolation Changes the Architecture
Section titled “Complete Isolation Changes the Architecture”A typical microfrontend architecture aims to integrate domain areas inside one jointly composed interface:
Shared Host├── jointly composed Interface├── consistent Navigation├── shared Platform Services├── integrated User Journey└── independently evolvable Domain AreasThe fully isolated boundary looks different:
Host Application└── embedded independent ApplicationImportant benefits remain. The embedded application can be deployed independently. It has a separate technical lifecycle and a clear failure boundary. A legacy estate can remain operational and be reduced gradually.
Other benefits become weaker or expensive. Seamless composition, end-to-end navigation, a consistent layout, shared platform mechanisms, integrated overlays, coherent accessibility, and centralized observability are no longer automatic. The host is no longer integrating only a domain slice. It is integrating a complete application with its own browser context.
The more strongly different framework versions must be isolated, the more the architecture shifts from composing microfrontends to integrating complete applications.
Arguing over whether the result still qualifies as a microfrontend is not particularly useful. The more important question is: Which architectural benefit did we want from microfrontends, and how much of it remains after the required isolation?
Complete isolation is possible. It is achieved by moving away from the microfrontend composition that was originally intended.

Avoiding the Migration Does Not Make It Disappear
Section titled “Avoiding the Migration Does Not Make It Disappear”As a permanent state, a broad version mix may mean several framework runtimes, increasing memory consumption, a growing compatibility matrix, and different toolchains. Platform tests must cover more combinations. Debugging requires knowledge of several framework generations. Security and maintenance cycles diverge. In the extreme case, the interface is assembled from complete applications whose integration permanently requires dedicated infrastructure.
An architecture is not economical merely because every team can avoid its local migration.
The framework migration avoided by one team may be paid for over years through additional runtime, integration, and operational work. A supposedly independent release saves coordination in the short term while the platform, support teams, and users carry the follow-up cost.
This is not a blanket prohibition. Decoupling a migration over time can be sensible. The cost must then be understood as an investment in a transition. Without a target state, the same investment becomes permanent operating expense.
A team’s technical freedom is not free. Its cost appears elsewhere in the overall system.
The Legitimate Exception: Controlled Migration
Section titled “The Legitimate Exception: Controlled Migration”An application rarely remains on a significantly older framework version without reason. Broad domain scope, many teams and dependencies, insufficient test automation, incompatible third-party libraries, custom build mechanisms, strong technical coupling, and critical business processes can make a direct migration irresponsible. Several technical foundations may need to change at once while available capacity remains limited.
Telling such a system to “just upgrade it” is not an architecture decision. It is a refusal to consider risk.
This is precisely where strong isolation can make sense:
Phase 1├── new Host└── complete Legacy Application in an iframe
Phase 2├── first new Capability└── reduced Legacy Application in an iframe
Phase 3├── further new Capabilities└── remaining Legacy Area in an iframe
Target└── iframe is removedIn a migration scenario, the iframe contains an estate that cannot yet be changed safely. In permanent operation, it contains the consequences of a missing shared version strategy.
That distinction matters. During a migration, the additional integration work is accepted deliberately to reduce replacement risk. Every extracted capability makes the isolated estate smaller. The architecture is moving toward a defined target.
In large transformation programs, attempts to separate framework generations that are far apart often end with an iframe. That is not necessarily a lack of technical creativity. It frequently shows that only a separate document can provide the required isolation reliably.
This practical observation is not universal proof and does not make the iframe a default solution. It simply acknowledges that large systems with a long history sometimes have no tighter integration option that is also sufficiently safe.
The iframe is not the target state. It is a controlled boundary around an estate that cannot yet be migrated safely.
Migration Needs a Target State
Section titled “Migration Needs a Target State”Multiple major versions can form a sensible transition:
Starting Point└── all Areas on the old Version
Transition├── first Areas on the target Version└── remaining Areas on the old Version
Target└── all active Areas within the agreed Version RangeThe difference between transition and permanent state is not the technology. In both cases, several runtimes may exist in parallel. The difference lies in ownership, limits, and direction.
A controlled exception needs at least a defined target version, a responsible owner, a migration sequence, and a removal plan. Supported version combinations must be documented. Performance budgets and integration costs should be observable. Above all, the exception needs a time limit and regular review.
Microfrontends can reduce the scope of a migration. A team may not have to upgrade the entire application in one release. They do not replace a migration strategy.
The ability to migrate gradually is not a recommendation for a permanent version mix.
When the Transition Becomes Permanent
Section titled “When the Transition Becomes Permanent”Version drift rarely results from one major decision. It grows from many understandable exceptions:
Year 1├── Angular 17├── Angular 19└── Angular 22
Year 2├── Angular 17├── Angular 21└── Angular 23One area migrates, another remains where it is, and a new one starts on the current target version. Every local decision may be defensible. In aggregate, the oldest application still moves further away from the rest of the platform.
Migrations do not become smaller. They become larger. Shared libraries must support several generations or be split as well. Toolchains, browser support, and maintenance cycles diverge. Knowledge of older versions must be retained indefinitely. The test matrix grows while exceptions become normal.
The situation becomes especially problematic when no one owns the technical platform as a whole. Each team then treats its version as a local concern even though users execute all of those versions inside the same product.
Version tolerance is a tool for transitions, not permission for permanent technical fragmentation.
Otherwise, a deferred migration becomes distributed technical debt.
The Framework Version Is a Platform Contract
Section titled “The Framework Version Is a Platform Contract”Inside a jointly composed interface, one central framework major version or a narrowly defined supported version range should be the normal state. Multiple major versions should remain a documented exception.
A shared workspace can support this governance. Tools such as Nx make centralized dependency versions, project relationships, affected applications, and coordinated migration runs visible. CI rules can prevent unnoticed version drift and control documented exceptions.
Nx does not solve the problem automatically. The monorepo does not create the shared version. The organization must enforce it as a technical platform contract. The tool merely makes that contract more visible and easier to automate.
The same principle can be applied across multiple repositories. Central policies, automated update processes, CI checks, and a defined compatibility matrix can provide the same governance. The repository boundary is not decisive. Shared responsibility for the runtime platform is.
A single-version policy does not mean that every team must coordinate every product release on the same day. It means that the framework runtime is not treated as an arbitrarily private decision while the applications are composed inside the same product and browser context.
A Clear Recommendation
Section titled “A Clear Recommendation”The normal state should be one shared framework major version or a deliberately supported narrow version range.
A temporary version mix is legitimate as a controlled migration. It needs a documented reason, an owner, a target, and an expiry date. New remotes should begin on the target version. Old versions should not be reinforced through additional consumers and new dependencies. Additional runtimes and iframes need a removal plan from the outset.
An arbitrary permanent version mix without a target state should be avoided. It confuses technical flexibility with architectural freedom and local convenience with economic sustainability.
Framework versions are part of the platform. Anyone operating several of them at once must prove more than the fact that they can be loaded. They must also own the runtime cost, shared browser resources, integration boundaries, and long-term maintenance.
Conclusion
Section titled “Conclusion”Multiple major versions are technically executable. A shared runtime, however, requires a shared version contract. Separate runtimes increase resource and testing effort while still sharing the same browser context.
Complete isolation is possible through an iframe. At that point, however, the architecture no longer composes only domain slices; it integrates independent applications inside one another. As a permanent target architecture, that price is usually neither economically nor architecturally sensible.
The same isolation can be justified as a temporary migration bridge. It protects an estate that cannot yet be changed safely and enables gradual replacement. For that to work, it needs a target state, clear ownership, and a planned removal.
Microfrontends may decouple a migration. They should not be used as a reason to avoid it permanently.