Article Guide
Architect15 min readJune 13, 2026

Shared Dependencies and Versioning in Micro Frontends

Learn how to manage shared dependencies in micro frontend architecture, including React singleton, design system versioning, shared libraries, dependency conflicts, contract compatibility, and production governance.

Tags:
Micro FrontendsShared DependenciesVersioningModule FederationFrontend Architecture

Shared dependencies are one of the most dangerous parts of micro frontend architecture.

Micro frontends promise independent ownership and independent deployment.

But if dependencies are shared badly, the system becomes tightly coupled again.

Common problems include:

  • Duplicate React bundles
  • Multiple React instances
  • Broken hooks or context
  • Design system version mismatch
  • Runtime dependency conflicts
  • Large JavaScript payloads
  • Incompatible remote versions
  • Hidden coupling through shared libraries

A strong micro frontend system needs clear dependency rules.

This article explains how to manage shared dependencies and versioning in micro frontends from senior frontend to architect level.

1. Why Shared Dependencies Matter

In a micro frontend system, multiple applications may run together in one browser page.

Example:

textEditor
Shell App
├── Catalog Remote
├── Cart Remote
├── Checkout Remote
├── Profile Remote
└── Orders Remote

Each app may use:

textEditor
React
React DOM
Router
Design system
Date utilities
Analytics SDK
Auth SDK
State library
API clients

If every remote bundles its own copy of every dependency, the page can become slow and unstable.

If every dependency is shared globally, the apps become tightly coupled.

The challenge is balance.

2. Core Principle

The most important rule is:

Share platform-level dependencies carefully. Keep domain business logic inside the owning remote.

Good shared dependencies:

  • React
  • React DOM
  • Design system runtime
  • Theme tokens
  • Analytics SDK
  • Auth provider
  • Stable utility packages

Risky shared dependencies:

  • Domain API clients
  • Business rules
  • Reducers
  • Feature-specific hooks
  • Remote internal components
  • Checkout validation logic
  • Cart calculation logic

If too much is shared, independent deployment becomes an illusion.

3. Types of Dependencies

Not all dependencies should be treated the same.

Dependency TypeExampleSharing Strategy
Runtime frameworkReact, Vue, AngularUsually shared carefully
RendererReact DOMUsually singleton
Design systemButton, Modal, tokensShared with strict versioning
Platform SDKAuth, analytics, feature flagsShared or shell-owned
Utility librarydate-fns, lodashCase-by-case
RouterReact RouterBe careful
State libraryRedux, ZustandAvoid global sharing by default
API clientCart API clientUsually domain-owned
Business logicCheckout rulesDo not share globally
Internal componentsProductCard internalsDo not expose casually

This table should guide dependency decisions.

4. What Is a Shared Dependency?

A shared dependency is a library that the host and remotes agree to use together instead of each bundling their own copy.

In Module Federation terms, dependencies can be configured as shared.

Conceptually:

textEditor
Shell App uses React
Cart Remote uses React
Catalog Remote uses React

Instead of loading React three times,
the system can share one compatible React instance.

This can reduce bundle duplication and prevent runtime issues.

But sharing must be intentional.

5. What Is a Singleton Dependency?

A singleton dependency means only one instance of the dependency should exist at runtime.

React is the most common example.

Why?

Because multiple React instances can break assumptions around:

  • Hooks
  • Context
  • React reconciler behavior
  • Shared providers
  • Design system context

Example problem:

textEditor
Shell uses React instance A.
Cart Remote bundles React instance B.
A component uses context from instance A.
Remote reads context through instance B.
Unexpected behavior occurs.

For React-based micro frontends, React and React DOM are usually configured as singletons.

6. React Singleton Strategy

Recommended:

  • React → singleton
  • React DOM → singleton

Reason:

  • Avoid duplicate React instances.
  • Avoid hooks/context issues.
  • Reduce bundle size.
  • Keep rendering behavior consistent.

But singleton does not remove the need for version discipline.

Bad:

Shell expects React 18. Remote requires React 19-only APIs. Runtime compatibility breaks.

Good:

All remotes follow approved React version policy. Upgrades are coordinated through platform governance.

Strong interview phrase:

React should usually be shared as a singleton, but singleton does not replace version governance.

7. React DOM Singleton

React DOM should normally match React.

Bad:

React 18 with incompatible React DOM version

Good:

React and React DOM follow the same approved version range.

Why it matters:

  • Rendering behavior
  • Hydration
  • Concurrent features
  • Root creation
  • Event system

React and React DOM should be treated as platform-level dependencies, not random per-team choices.

8. Design System Sharing

A shared design system is usually required in large micro frontend systems.

It provides:

textEditor
Buttons
Inputs
Modals
Cards
Typography
Spacing
Theme tokens
Accessibility patterns
Loading states
Error states

Without a shared design system, micro frontends can feel like separate products.

Bad result:

  • Catalog uses one button style.
  • Cart uses another modal behavior.
  • Checkout uses different form validation UI.
  • Profile uses different spacing and typography.

Good result:

  • All remotes use the same design tokens and approved components.

9. Design System Versioning

Design system versioning is tricky.

If every remote uses a different version, UI inconsistency grows.

If all remotes must upgrade together, independent deployment suffers.

Possible strategies:

Strategy 1: Strict Single Version

All remotes use design-system@2.4.0

Benefit:

  • Maximum consistency

Risk:

  • Coordinated upgrades can slow teams

Strategy 2: Compatible Version Range

Allowed versions: 2.x

Benefit:

  • More flexibility

Risk:

  • Minor visual differences possible

Strategy 3: Token Stability + Component Versioning

Design tokens stable Components versioned Breaking changes controlled

This is often the most practical enterprise model.

10. Design System Upgrade Policy

A good policy:

  • Breaking changes require migration guide.
  • Design tokens should be backward compatible.
  • Deprecated components have removal timelines.
  • Critical accessibility fixes are prioritized.
  • Visual regression tests run across major remotes.
  • Upgrade windows are communicated.

Design system changes can affect many remotes.

Treat them like platform changes.

11. Router Dependency Concerns

Routing is sensitive in micro frontends.

If shell and remotes all use routing libraries independently, issues can happen.

Example:

  • Shell uses React Router.
  • Profile Remote uses React Router.
  • Checkout Remote uses custom routing.

This is not always wrong, but ownership must be clear.

Recommended:

  • Shell owns top-level routing.
  • Remote owns nested routing.
  • Navigation uses URL boundaries.

Avoid:

  • Remote directly mutates shell router internals.
  • Shell parses every remote internal route.
  • Multiple routers fight over browser history.

Router dependencies should be managed carefully.

12. Utility Library Sharing

Utility libraries like lodash, date-fns, or formatting helpers may be shared.

But do not over-optimize too early.

Questions to ask:

  • Is the dependency large?
  • Is it used by many remotes?
  • Is the version stable?
  • Will sharing create coupling?
  • Is duplication cheaper than runtime coordination?

Small utility duplication may be acceptable.

A large shared SDK may need governance.

13. Auth SDK Sharing

Auth is usually platform-level.

Options:

  • Shell owns auth SDK.
  • Shell provides safe identity context.
  • Remotes call APIs through approved client.

Avoid:

  • Every remote initializes its own auth SDK.
  • Every remote handles token refresh.
  • Every remote stores tokens differently.

Auth dependency decisions affect security.

Usually, auth should be centralized or platform-owned.

14. Analytics SDK Sharing

Analytics often crosses all remotes.

Recommended:

  • Shell initializes analytics SDK.
  • Remotes emit domain events.
  • Platform analytics layer normalizes and sends events.

This avoids each remote implementing analytics differently.

Example events:

  • catalog:filter-applied
  • cart:item-added
  • checkout:payment-submitted
  • profile:address-updated

Analytics SDK can be shared, but event contracts must be governed.

15. State Library Sharing

Sharing a state library is not the same as sharing state.

Example:

All remotes may use Zustand.

This is different from:

All remotes use one global Zustand store.

Sharing the library can be okay.

Sharing one global store is risky.

Bad:

  • Catalog writes into global store.
  • Cart reads catalog slice.
  • Checkout mutates cart slice.
  • Profile depends on checkout state.

This creates tight coupling.

Recommended:

  • Each remote owns its domain state.
  • Shell owns only platform state.
  • Backend owns business-critical state.

16. API Client Sharing

API clients are often better domain-owned.

Bad:

textEditor
sharedApiClient
├── catalog APIs
├── cart APIs
├── checkout APIs
├── orders APIs
└── profile APIs

If every remote imports one giant API client, domains become coupled.

Better:

  • Catalog Remote -> Catalog API client
  • Cart Remote -> Cart API client
  • Checkout Remote -> Checkout API client
  • Orders Remote -> Orders API client

Shared low-level HTTP utilities may be fine.

But domain API clients should usually stay with domain ownership.

17. Business Logic Sharing

Avoid sharing domain business logic casually.

Bad shared logic:

  • cartCalculationRules
  • checkoutPaymentRules
  • productRankingRules
  • orderEligibilityRules
  • promotionEngine

Why?

  • Business logic changes frequently.
  • Ownership becomes unclear.
  • Versioning becomes risky.
  • Different domains may need different behavior.
  • Backend should often own critical business rules.

If business logic is critical, it likely belongs in backend services or domain-owned packages with strict contracts.

18. Hidden Coupling Through Shared Libraries

Shared libraries can hide coupling.

Example:

textEditor
shared-commerce-utils
├── calculateCartTotal
├── getProductFilters
├── validateCheckoutStep
├── formatOrderStatus
└── applyPromotion

At first, this looks convenient.

Later, every remote depends on it.

One change can break everyone.

This becomes a shared monolith.

Better:

shared-platform-utils -> stable, generic utilities domain logic -> domain-owned

19. Version Mismatch Problems

Version mismatch can break at runtime.

Examples:

  • Shell expects CartPage prop: userId. Cart Remote changed prop to customerId.
  • Shell listens for cart:updated. Cart Remote emits cart:item-updated.
  • Shell expects design-system Button v2 behavior. Remote uses Button v3 with breaking change.
  • Remote requires React feature not supported by shell’s React version.

These are not always caught at build time.

Because apps deploy independently, runtime compatibility matters.

20. Semantic Versioning

Semantic versioning helps communicate change impact.

textEditor
MAJOR.MINOR.PATCH

Meaning:

  • PATCH = bug fix, no breaking change
  • MINOR = backward-compatible feature
  • MAJOR = breaking change

Example:

  • design-system 2.4.1 -> patch
  • design-system 2.5.0 -> minor
  • design-system 3.0.0 -> major breaking change

But semantic versioning only works if teams follow it honestly.

A “minor” release with breaking changes is dangerous.

21. Contract Versioning

Micro frontends need contract versioning, not only package versioning.

Contracts include:

  • Exposed modules
  • Props
  • Events
  • Payloads
  • Routes
  • Shared context
  • Feature flags
  • Design system expectations

Example contract:

textEditor
Cart Remote exposes:
./CartPage

CartPage props:
{
  userId: string;
  locale: string;
  currency: string;
}

Events:
cart:updated
Payload:
{
  cartId: string;
  itemCount: number;
}

Changing this requires compatibility planning.

22. Backward Compatibility

Independent deployment is safer when changes are backward compatible.

Bad breaking change:

Remove itemCount from cart:updated payload.

Better migration:

  • Step 1: Add new field itemSummary while keeping itemCount.
  • Step 2: Update consumers to use itemSummary.
  • Step 3: Verify all consumers migrated.
  • Step 4: Remove itemCount in next major version.

Backward-compatible changes allow staggered deployments.

This is critical when shell and remotes are released independently.

23. Version Pinning

Version pinning means using a specific approved version.

Example:

  • checkoutApp -> 1.4.2
  • cartApp -> 1.8.4
  • catalogApp -> 2.3.1

This can be done through a manifest.

Benefits:

  • Predictable runtime behavior
  • Rollback support
  • Auditability
  • Safer critical flows

Risk:

  • More release coordination
  • Version management overhead

Use version pinning for critical domains like checkout.

24. Version Ranges

Version ranges allow compatible flexibility.

Example:

design-system: ^2.4.0

This means compatible 2.x upgrades may be accepted.

Benefits:

  • Less manual coordination
  • Faster patch adoption

Risk:

  • Unexpected behavior if compatibility is not truly maintained

For critical shared dependencies, version ranges should be controlled by platform policy.

25. Remote Manifest Versioning

A remote manifest can track active versions.

Example:

jsonEditor
{
  "cartApp": {
    "version": "1.8.4",
    "url": "https://cdn.company.com/cart/1.8.4/remoteEntry.js",
    "owner": "cart-team",
    "rollbackVersion": "1.8.3"
  },
  "checkoutApp": {
    "version": "1.4.2",
    "url": "https://cdn.company.com/checkout/1.4.2/remoteEntry.js",
    "owner": "checkout-team",
    "rollbackVersion": "1.4.1"
  }
}

This enables:

  • Controlled rollout
  • Environment promotion
  • Rollback
  • Version visibility
  • Incident ownership

26. Dependency Governance

Large micro frontend systems need governance.

Platform team should define:

  • Approved React version
  • Approved shared dependency list
  • Design system version policy
  • Dependency upgrade process
  • Breaking change policy
  • Security patch process
  • Bundle budget policy
  • Shared library ownership
  • Contract testing requirements

Governance prevents chaos.

But it should not block teams unnecessarily.

Strong phrase:

The goal is safe autonomy, not unlimited freedom.

27. Dependency Upgrade Strategy

A safe upgrade strategy:

  • 1. Identify dependency and affected remotes.
  • 2. Check breaking changes.
  • 3. Upgrade shell or platform layer first if needed.
  • 4. Test remotes in preview shell.
  • 5. Run contract and integration tests.
  • 6. Roll out gradually.
  • 7. Monitor errors and Web Vitals.
  • 8. Keep rollback option.

For React or design system upgrades, do not let every team upgrade randomly.

Coordinate through platform governance.

28. Security and Dependency Risk

Shared dependencies can introduce security risk.

Risks:

  • Vulnerable dependency used by many remotes
  • Untrusted remote loads compromised code
  • Old design system version has accessibility/security bugs
  • Auth SDK mismatch causes token handling issues

Security practices:

  • Dependency scanning
  • Approved package registry
  • Lockfile policy
  • SBOM if required
  • Patch process
  • CDN artifact integrity
  • Trusted remote origins

Micro frontends increase the number of deployable artifacts, so dependency security must be systematic.

29. Performance Impact

Dependency sharing affects performance.

Performance risks:

  • Duplicate React bundles
  • Duplicate design system bundles
  • Large shared vendor chunks
  • Multiple date libraries
  • Remote loading waterfall
  • Unnecessary polyfills per remote

Track:

  • Bundle size per remote
  • Shared dependency duplication
  • Route-level JavaScript size
  • Remote load time
  • Web Vitals

A shared dependency strategy should improve performance without creating excessive coupling.

30. Common Anti-Patterns

Anti-PatternWhy It Is Bad
Sharing every libraryCreates hidden coupling
Sharing business logic globallyCreates distributed monolith
No React singletonDuplicate React/runtime issues
Every remote uses random React versionsRuntime instability
Design system breaking changes without migrationUI breakages
One giant shared utils packageHidden domain coupling
Shared global storeTight coupling
No contract versioningRuntime breakages
No manifest/version visibilityDebugging and rollback harder
No dependency governanceArchitecture chaos

31. Interview Questions

Q1. How do you share dependencies safely in micro frontends?

Share only truly common platform-level dependencies. React and React DOM are usually shared as singletons. The design system can be shared with strict versioning. Business logic and domain API clients should usually stay inside the owning remote. Shared dependency decisions should be governed by platform rules.

Q2. Why should React be a singleton?

React should usually be singleton to avoid multiple React instances on the same page. Multiple instances can cause problems with hooks, context, rendering behavior, and bundle size. But singleton still requires version governance.

Q3. Should all remotes share one global store?

Usually no. Sharing one global store tightly couples independently deployed remotes to one state shape. It makes versioning, testing, and ownership harder. Prefer remote-owned state, URL state, backend state, and small platform-level shared context.

Q4. How do you manage design system versions?

Use a clear versioning policy, backward-compatible tokens, migration guides for breaking changes, visual regression tests, and coordinated upgrades for major versions. The design system should support consistency without blocking every team.

Q5. What happens if shell and remote use incompatible dependency versions?

You can get runtime crashes, duplicated bundles, broken hooks/context, design inconsistencies, or contract mismatches. This should be prevented through version policy, contract tests, CI checks, and manifest-controlled rollout.

Q6. What should not be shared?

Internal reducers, private hooks, business rules, checkout validation logic, cart calculation rules, domain API clients, and unstable internal components should not be shared casually. These create hidden coupling and reduce team autonomy.

32. Strong Senior Answer

If an interviewer asks:

“How would you manage shared dependencies in micro frontends?”

A strong answer:

I would classify dependencies into platform dependencies and domain dependencies. Platform dependencies like React, React DOM, design system, auth SDK, analytics SDK, and feature flag client can be shared carefully. React and React DOM should usually be shared as singletons to avoid duplicate React instances and hooks/context problems. But I would avoid sharing domain business logic, internal reducers, domain API clients, or feature-specific hooks across remotes. Those should stay inside the owning domain or backend services. For versioning, I would use platform governance. The platform team should define approved versions, compatibility ranges, breaking change rules, and upgrade processes. I would also use contract tests for exposed modules, props, events, and shared context. For runtime remotes, I would track active versions through a manifest so we can control rollout and rollback. The key is to share enough to keep the product consistent and performant, but not so much that independent micro frontends become tightly coupled.

33. Final Checklist

  • Are React and React DOM shared safely?
  • Is there an approved React version policy?
  • Is the design system versioned?
  • Are design system breaking changes governed?
  • Are business rules kept out of shared utilities?
  • Are domain API clients owned by domains?
  • Is global shared state avoided?
  • Are contract versions documented?
  • Are exposed modules stable?
  • Are event payloads versioned?
  • Is the remote manifest tracking active versions?
  • Are dependency conflicts checked in CI?
  • Are duplicate bundles monitored?
  • Are security vulnerabilities scanned?
  • Is rollback possible if a dependency upgrade breaks production?

34. Summary

Shared dependencies can make or break a micro frontend architecture.

Good sharing gives:

  • Consistent React runtime
  • Smaller bundles
  • Shared design system
  • Consistent platform behavior
  • Better user experience

Bad sharing creates:

  • Tight coupling
  • Runtime conflicts
  • Hidden dependencies
  • Version chaos
  • Distributed monolith
  • Production instability

Recommended model:

textEditor
Share platform-level dependencies carefully.
Keep domain logic inside domains.
Use singleton for React and React DOM.
Version the design system responsibly.
Avoid global shared state.
Use contracts and manifests for compatibility.
Govern upgrades through platform standards.

The strongest takeaway:

In micro frontends, dependency sharing is not just a bundler configuration. It is an architecture governance decision.

References

  • webpack Module Federation Documentation (https://webpack.js.org/concepts/module-federation/)
  • Module Federation Official Site (https://module-federation.io)
  • Micro Frontends — Martin Fowler (https://martinfowler.com/articles/micro-frontends.html)
  • Micro Frontends (https://micro-frontends.org)
  • AWS Prescriptive Guidance: Micro-frontends (https://docs.aws.amazon.com/prescriptive-guidance/latest/micro-frontends-aws/introduction.html)