How do you share common functions/libraries in a microfrontend app
Options: Module Federation shared dependencies (share React etc. as singletons), a versioned internal npm package / monorepo workspace, or import maps. Trade-offs: avoid duplicate bundles and version conflicts vs. coupling. Singletons matter for stateful libs like React; design-system and utils are good share candidates.
In a microfrontend (MFE) architecture, multiple independently-deployed apps run together — and you don't want each one shipping its own copy of React, your design system, and shared utils. The question is how to share without re-coupling them.
The options
1. Module Federation (Webpack/Vite) — shared dependencies. The MFE-native answer. Each app declares shared deps; at runtime, Module Federation loads a single shared instance rather than each app bundling its own:
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true },
"@company/design-system": { singleton: true },
}singleton: true is critical for stateful libraries — two copies of React (or a router, or a state store) breaks hooks, context, and instanceof checks. requiredVersion negotiates compatibility.
2. A versioned internal package. Publish shared code (@company/ui, @company/utils) to a private registry; each MFE depends on it like any npm package. Simple and explicit, but each app bundles its own copy (duplication) and apps can drift to different versions. A monorepo with workspaces keeps versions aligned and makes local development easy.
3. Import maps / a shared global. Native browser import maps point a bare specifier (react) at one URL all MFEs load — true single instance, no bundler coupling. Less common, but the standards-based path.
The trade-off at the center
- Share more → smaller total bundle, guaranteed single instance, consistent UX — but tighter coupling: a shared-lib change can affect every MFE, and version negotiation gets hard. Defeats some of the MFE independence promise.
- Share less → full independence — but duplicate bundles, version drift, and inconsistent UX (two button styles).
A pragmatic split
- Always share as singletons: the framework (React/ReactDOM), router, shared state/context — anything where two instances break.
- Share, versioned: the design system and common utils — for consistency and bundle size, tolerating some coupling.
- Don't share: app-specific logic — that's the whole point of MFEs.
Other concerns
Version-conflict strategy (requiredVersion, fallbacks), keeping shared APIs stable (they're contracts now), and CI that tests MFEs against the shared versions they'll actually run with.
The framing
"You don't want every microfrontend shipping its own React and design system. Module Federation's shared config is the native answer — it loads one runtime instance, with singleton: true for stateful libs like React where two copies break hooks and context. Alternatively a versioned internal npm package (or monorepo workspace) — simpler but duplicated and prone to drift — or browser import maps. The core trade-off is bundle size and single-instance correctness versus coupling: I'd always singleton-share the framework and router, versioned-share the design system, and never share app-specific code."
Follow-up questions
- •Why does singleton: true matter for React specifically?
- •What goes wrong if two microfrontends load different React versions?
- •Versioned npm package vs Module Federation — when each?
- •How do you handle version conflicts between microfrontends?
Common mistakes
- •Letting each MFE bundle its own React — duplicate, and hooks/context break across boundaries.
- •Not marking stateful shared libs as singletons.
- •Over-sharing until MFEs are tightly coupled and can't deploy independently.
- •No version-negotiation strategy, leading to runtime conflicts.
Performance considerations
- •Sharing dependencies eliminates duplicate bundles across MFEs, cutting total download size. But the shared chunk becomes a critical dependency — version negotiation and fallback loading affect startup performance.
Edge cases
- •Two MFEs needing incompatible major versions of a shared lib.
- •A shared library's breaking change affecting all MFEs at once.
- •A shared stateful store loaded twice.
- •An MFE that must run standalone and within the shell.
Real-world examples
- •Webpack Module Federation sharing React and a design system across team-owned MFEs.
- •A monorepo with workspace packages for shared UI and utils consumed by each MFE.