Independent Releases Still Need Compatible Neighbors

The account team releases its new frontend. The shell stays on the previous version. Checkout was updated yesterday.
That can be a healthy arrangement. It is what independent deployment is meant to permit.
It is also a compatibility problem. Those versions need to work together, and users will discover the combinations we did not consider while each team tested its own application.
The earlier micro-frontends article asks whether organizational independence justifies the split. Here I want to examine the technical consequence after that decision: what independence requires during ordinary releases and rollbacks.
Separate three kinds of boundary
A code boundary determines which module can import another. An ownership boundary determines who may change it. A deployment boundary determines which artifact can be released without rebuilding the others.
They can align, but they do not have to.
A modular frontend can have feature packages, explicit APIs, separate owners, and enforced import rules while producing one application build. That is the modular monolith in this comparison, not a claim that all code belongs in one undifferentiated folder.
Micro-frontends add independently deployed application artifacts, composed through routes, runtime loading, frames, or another integration mechanism. Different composition methods have different costs. An iframe provides a stronger browser isolation boundary than mounting two modules in the same JavaScript runtime; it also changes interaction and integration constraints.
I would name the intended composition before discussing the benefits. "Micro-frontends" is otherwise doing too much work in the sentence.
One build moves compatibility work earlier
In a modular monolith, one build checks the selected package versions together. A failing interface change can stop the release before production sees it. A coordinated rollback returns that tested set of frontend modules together.
That does not guarantee correctness. Old tabs still exist, backend APIs still evolve, and integration tests can miss a workflow. But the frontend's internal version combination is more constrained.
With independent artifacts, compatibility work remains necessary and moves partly into runtime and release policy. The shell needs to understand the remote's contract; the remote needs to tolerate the supported shell versions.
The work did not disappear because CI pipelines became separate. We changed where it is paid.
A remote needs a supported contract
An illustrative mount contract might include a version, capabilities, lifecycle, and a constrained way to communicate:
type AccountModuleContract = { version: 2; mount: (container: HTMLElement, context: AccountContext) => () => void; capabilities: readonly ('profile' | 'notification-settings')[]; };
The shape is not a recommended universal micro-frontend API. It shows the questions a boundary needs to answer: what does the host provide, what may the remote assume, how does it clean up, and how can the host detect incompatibility?
Do not make shared context an unrestricted store containing every application's internals. That turns the mount API into a distributed import path with weaker verification.
Capabilities can make additive evolution easier than assuming that every participant upgrades simultaneously. For breaking changes, define a support window and migration path.
Release independence creates version combinations
If several remotes each support two versions while the shell also supports two, the possible combinations multiply. It is rarely practical to test every theoretical combination equally.
Constrain the supported matrix. Test each remote against the current shell and the declared compatibility window. Keep representative whole-product flows that cross the important boundaries.
A remote's unit tests may correctly show that its save action works. They do not show that the shell refreshes the account menu, preserves navigation, and clears the right state after logout.
Contract tests help, but a contract can be structurally valid and behaviorally wrong. The few shared workflows deserve integration tests owned by someone, not by an imaginary team named "the platform."
Runtime manifests are release artifacts
If the shell loads a remote entry through a mutable URL, the deployed composition can change without the shell's commit changing.
Record the selected remote versions or immutable asset references. Keep enough history to reconstruct what users were served. Rollback should select a known-compatible composition, not point several URLs at yesterday and hope.
Asset retention matters for clients that already loaded an old entry. Removing the old chunks immediately can break lazy navigation later, long after the initial page load succeeded.
CDN cache lifetimes can also cause different users to load different manifest versions. This is manageable if the contracts support overlap. It is inconvenient if independent deployment silently assumes instantaneous global consistency.
Sharing dependencies exchanges one cost for another
Shipping separate dependencies can increase download size and produce multiple copies of libraries. Sharing them at runtime can reduce duplication, but introduces version negotiation and lifecycle constraints.
React singletons deserve care when components and hooks interact across a shared runtime. A shared design-system library also has behavioral and styling contracts; two versions can differ in focus management or portal assumptions even when the buttons look similar.
I would not force every dependency into a singleton merely to improve a bundle report. Nor would I accept uncontrolled duplication without measuring the user cost. Decide which shared dependencies are part of the platform contract, and verify both upgrades and rollbacks.
For stronger isolation, route-level composition may be more straightforward than many independently loaded widgets sharing one screen. It depends on the product flow, not on which demo looked most flexible.
Isolation needs failure behavior
A remote can fail to load because of a network error, stale asset reference, or incompatible deployment. The shell still needs a useful route and a recoverable state.
Distinguish an optional sidebar from checkout's payment step. One may show a fallback. The other may need to stop the workflow and preserve user input. An error boundary catches some rendering failures; it is not full isolation from CSS conflicts, shared global state, or security problems.
Keep remote-loading timeouts, telemetry, retry rules, and fallback ownership explicit. A spinner with no deadline is not a graceful degradation strategy.
The modular monolith has fewer runtime composition failure points, but its deployment can affect more features at once. Feature flags, staged releases, and modular tests can reduce that risk without creating separate runtime applications.
Shared state is an authority question
Identity, cart state, locale, and navigation often cross frontend boundaries.
Decide who owns each truth. An event that says "cart changed" can prompt other applications to read the authoritative value. An event carrying a partial cart snapshot can accidentally create another source of truth with its own freshness problems.
An event bus should have typed contracts, lifecycle rules, and unsubscribe behavior. It should not become an excuse for every remote to discover undocumented events by reading the others' source.
Authorization must remain enforced at the backend. A remote being mounted by an authenticated shell does not give its API calls permanent authority.
These rules apply inside a modular monolith too. Independent deployment makes violations harder to repair together, which raises the value of keeping the shared surface small.
Try modularity before distributing a coordination problem
If the main pain is accidental imports, unclear ownership, or slow whole-repo CI, a modular build with boundary rules and affected-project checks may address it directly.
If teams genuinely need separate release schedules, existing applications cannot reasonably be merged, or one part must evolve under substantially different constraints, runtime composition may justify its cost.
I would trial the split at a boundary already understandable in the product, with a specific blocked workflow the trial is meant to improve. Measure release lead time and integration effort, then account for the platform work needed to keep it reliable.
A module boundary that cannot be described cleanly inside one build usually does not become clearer when a network load is inserted between its parts.
Independence has a maintenance agreement
The decision is not simply one deployment versus many. It is how much coordination we want to perform before release and how much compatibility we are prepared to maintain afterward.
I would choose a modular monolith when coordinated artifacts are affordable and the team's main need is better boundaries. I would choose micro-frontends when independent delivery solves a demonstrated constraint and the teams can own the resulting compatibility work.
Either arrangement can be maintained well. Neither relieves us of agreeing what a neighboring module may do while we are deploying something else.
If your teams already release independently, what happens when one of them rolls back? I'd be interested in the supported combination, because that answer says more about the architecture than the number of repositories.
About the author
Maksym Kuzmitskyi is a Senior Software Developer focused on React and TypeScript. His work includes enterprise applications, shared frontend components, Node.js integration, testing and accessibility. Alongside frontend work, he designs AI-assisted delivery workflows and has developed personal review tooling.
More than a blog post
I share frontend news and the reasoning behind it throughout the day. Pick the language that feels natural to you.