skip to content

Shared Dependencies

Frameworks and state libraries usually must be singletons, so microfrontends have to negotiate versions rather than each bringing their own. You will learn singleton enforcement, import-map overrides, and the bundle cost of getting it wrong.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

5

In a microfrontend architecture where several independently-built apps are combined on one page, what is the difference between sharing a UI library like React as a 'singleton' versus letting each microfrontend bundle its own 'scoped' copy, and why does the choice matter?

level: juniorimportance: must knowfreq 65%

answer

  1. one instance vs many copies
  2. React context needs one React
  3. coordination cost vs coupling cost
  4. module federation shared config
  5. invalid hook call = duplicate React

basics

~20 s

Singleton means every microfrontend uses one shared copy of a library loaded once on the page. Scoped means each microfrontend ships and uses its own private copy. Singleton saves download size but couples apps; scoped isolates apps but repeats code.

solid answer

~40 s

Singleton sharing designates one library (usually one with global/mutable state like React, a router, or a state manager) as 'there can only be one instance on the page.' The host or the module-federation runtime loads it once, and every microfrontend that declares it as shared reuses that instance instead of bundling its own. Scoped (non-shared, or 'isolated') sharing lets each microfrontend bundle and load its own copy, fully independent of what siblings use. Singleton is required for libraries that break with multiple instances -- React context, Redux stores, routing state -- because two React copies can't share hooks or contexts. Scoped is safer for stateless utility libraries (lodash, date formatting) where duplication only costs bytes, not correctness, and lets teams upgrade independently without coordinating a shared version.

go deeper

for a junior

Should articulate the basic distinction -- one shared copy vs each app having its own -- and give a rough intuition that React-like libraries need singletons while utility libraries don't.

for a middle

Should name the module federation shared/singleton config concretely and explain why context/hooks break with duplicate instances.

for a senior

Should discuss the coordination cost singleton sharing imposes on team independence and describe concretely how version mismatches are handled (fallback vs strict failure).

for a principal

Should reason about this as an organizational/architectural trade-off: which libraries justify the coupling cost of a shared singleton contract across teams, and how to draw that boundary as the platform scales.

## Why duplicate copies appear When a page is assembled from several independently deployed microfrontends -- say a checkout team's app, a navigation team's app, and a recommendations team's app -- each one is typically built with its own copy of shared libraries like React, a state manager, or a design-system component kit baked into its bundle. If every microfrontend simply ships its own copy, the browser ends up downloading and running multiple copies of the same library side by side on one page. - For **stateless libraries** this is only wasteful. - For libraries that carry **global or module-level state**, it is actively broken, because JavaScript module resolution is per-bundle -- two independently bundled copies of React are two entirely different objects in memory, even though the source code is identical. ## The mechanism that fixes it The mechanism that fixes this is 'singleton' sharing, most commonly configured through a module-federation-style runtime (webpack/Rspack Module Federation's `shared` config, or an equivalent native-federation setup). Each microfrontend declares which packages it wants to share, at what version range, and with `singleton: true`. At runtime, the federation runtime resolves all the declared shared dependencies from every loaded microfrontend into one shared scope. 1. The **first** microfrontend to request a singleton package gets to provide it (or the host provides it ahead of time). 2. Every **subsequent** microfrontend that also declares that package as a singleton is handed the exact same module instance instead of loading its own bundled copy, even though webpack still emitted separate chunks containing that code as a fallback. **Scoped** (non-singleton) sharing is the default absence of this mechanism: nothing is deduplicated, and each remote's own bundled copy is used, isolated in its own module scope. ## Why the distinction exists The reason this distinction exists is that a browser page is a single JavaScript runtime shared by every microfrontend on it, but the libraries were built as if each microfrontend were the only app on the page. Libraries built around a single global instance assume there is exactly one of them holding state that every component reads and writes: - React's reconciler and its Context API - a Redux/Zustand store - a routing library's history object - a design system's theme provider If two microfrontends each load their own React, a component tree rendered by microfrontend A and a `useContext` call made by microfrontend B's component will not see each other's state, because they belong to different React instances with different internal fiber trees. Singleton sharing forces there to be exactly one such instance, restoring the assumption the library was written under. ## The trade-off The trade-off cuts directly against microfrontend architecture's core promise of independent deployability. | Mode | What it buys | What it costs | |---|---|---| | **Scoped sharing** | keeps microfrontends fully decoupled: team A can upgrade to a new React version the day it ships without asking team B to do anything | at the cost of extra bytes downloaded (a duplicated React runtime is roughly 40-45KB gzipped, multiplied by however many microfrontends bundle it) and, for stateful libraries, outright breakage rather than just waste | | **Singleton sharing** | eliminates the duplication and the cross-instance breakage | but it re-introduces a coordination dependency between teams: everyone sharing a singleton is implicitly agreeing to stay on a mutually compatible version, which is exactly the kind of lockstep-release coupling microfrontends are usually adopted to avoid | Teams typically resolve this by singleton-sharing only the handful of libraries that truly require a single instance (framework, router, global state, design-system context providers) and leaving everything else -- utility libraries, one-off widgets, anything without shared mutable state -- scoped. ## How it shows up in production - **Singleton misconfiguration** shows up as a specific, recognizable class of bug: 'Invalid hook call,' 'cannot read properties of null (useContext),' or a UI theme/provider silently not applying to a nested microfrontend, all because a second, un-deduplicated copy of the library snuck in -- often because one microfrontend declared a version range the federation runtime judged incompatible with what was already loaded, so it silently fell back to bundling its own copy rather than failing loudly. - Conversely, **over-eager singleton sharing** shows up as deploy-time breakage: microfrontend A ships a major version bump of a shared library, and because it's a strict singleton with no fallback, every other microfrontend that hard-depends on the old API breaks in production the moment A deploys, even though A's own code works fine. ## A worked example A concrete, widely referenced example is Webpack Module Federation's own documented pattern: a host app and several remotes all declare `react` and `react-dom` as `{ singleton: true, requiredVersion: '^18.0.0' }` in their `shared` config, while a utility like `dayjs` is left unshared because each remote bundling its own few kilobytes of date formatting causes no correctness issue, only a negligible size cost.

  • Which kinds of libraries almost always need to be singletons, and which are usually safe to leave scoped?
    Libraries with module-level or global mutable state that consumers implicitly share -- the UI framework itself (React/Vue), routing libraries, global state stores, and any context/theme provider -- need to be singletons because two instances can't see each other's state. Pure, stateless utility libraries like lodash, date formatters, or validation helpers are safe to leave scoped since duplication only costs bytes, not correctness.
  • If two microfrontends declare incompatible major versions of a singleton library, what typically happens instead of a hard error?
    Most federation runtimes don't hard-fail by default; they emit a console warning and let the version that was loaded first (or the host's version) win, silently falling back to letting the other microfrontend bundle and use its own separate copy. This 'silent fallback' is exactly what re-introduces the multiple-instance bug the singleton was meant to prevent, which is why many teams also set `strictVersion: true` to fail loudly instead.
  • How does singleton sharing affect a microfrontend's ability to deploy independently?
    It reintroduces coupling: since every consumer must accept the same instance, someone has to decide whose version 'wins' when ranges diverge, effectively requiring the sharing teams to coordinate compatible version bumps. This is a direct tension with the independent-deployability goal that motivates microfrontends in the first place.

Singleton sharing is like a shared office printer everyone queues for -- one physical device, consistent state, but if someone changes its settings it affects everyone. Scoped sharing is like everyone buying their own personal printer -- no coordination needed, but you pay for N printers and they can't hand paper to each other mid-job.

saying these in an interview costs you the question

  • Says microfrontends always share every dependency to save bytes
  • Doesn't know why two React copies break context/hooks
  • Thinks scoped sharing has no downsides at all
  • Can't explain what happens on a version mismatch
  • Assumes singleton sharing requires all teams to release in lockstep with zero fallback options

context

open as a page

When a host application and several remote microfrontends each declare a required version range for a shared library (for example one remote needs '^17.0.0' and another needs '^18.2.0'), how does a module-federation-style runtime decide which actual version gets loaded on the page, and what options does it give you to control that decision?

level: middleimportance: must knowfreq 60%

basics

~20 s

The runtime looks at every version range each app asked for, tries to find one already-loaded version that satisfies all of them, and uses that. If none satisfies everyone, or if you set stricter rules, it either loads a second separate copy for the mismatched app or throws an error, depending on your configuration.

open as a page

In production, what concrete symptoms show up when a library that was supposed to be a singleton across microfrontends ends up loaded twice on the same page, and how would you diagnose that it's a duplicate-instance problem rather than an application bug?

level: seniorimportance: must knowfreq 55%

basics

~20 s

You'll see weird bugs like broken shared state, theme/context not applying somewhere, or a hooks crash -- but only for one part of the page, not everywhere. Checking the browser's network tab shows the same library file downloaded twice, which confirms two separate copies are running.

open as a page

What is an import map, and how do import-map overrides let a team swap which build of a shared dependency loads for a specific microfrontend or environment without rebuilding and redeploying every app on the page?

level: seniorimportance: should knowfreq 35%

basics

~20 s

An import map is a config that tells 'import react' to actually fetch a specific URL. Overriding one entry lets you redirect just that library to a different file -- like a staging build or hotfix -- without touching or rebuilding any of the apps that import it.

open as a page

Given that sharing a dependency as a singleton across microfrontends avoids duplicate downloads and duplicate-instance bugs, under what circumstances would an architect deliberately choose NOT to share a dependency and accept the duplication instead?

level: principalimportance: should knowfreq 30%

basics

~20 s

When the cost of duplication (a bit more download size) is smaller than the cost of forcing every team to agree on one version. If teams need to move independently, the library is small, or one team is on an incompatible version on purpose, it's often better to just let it duplicate.

open as a page