Choosing an architecture
Teams adopt microfrontends to create a release boundary: Product A can ship UI into another team’s shell without releasing either codebase together.
A full MFE platform supplies routing, rollout controls, version policy, and operational tooling. A component or region with a stable contract may need a runtime loader and a manifest.
Choose the smallest composition layer that covers the release model. mountly can serve as that layer for widgets and regions with separate releases. Route-level applications with coordinated rollout requirements may need a broader platform.
Start with the boundary you need
Section titled “Start with the boundary you need”Framework-native tools fit these cases:
- One React app where every surface lives in the same bundle → use
React.lazy,next/dynamic, or framework-native lazy routes. See When not to use mountly. - Every region shares one Router, Query client, or Redux store → stay in one app shell.
- You need SSR plus hydration of server-rendered HTML → use Astro, Next, or Remix islands.
mountly fits these cases:
- You ship widgets into hosts you do not fully control (CMS, static HTML, another team’s platform).
- You need HTML-addressable components (
<product-a-settings>). - Teams need independent widget or region deploys.
- You want intent-based loading without writing a lazy-loader per button.
Decision ladder
Section titled “Decision ladder”Comparison at a glance
Section titled “Comparison at a glance”| Approach | When | Tradeoff |
|---|---|---|
| Monorepo npm lib | Same org, shared CI is OK | Merge coordination |
| mountly widget drop-in | Foreign host with embedded interactive regions | Import map / version pinning |
| mountly manifest verticals | Independent CDN deploys into one page shell | Shared React version agreement |
| mountly iframe widgets | Untrusted or legacy verticals, one shared page | A framework bootstrap per widget |
| Module Federation | Deep horizontal integration, shared build graph | shared blocks, tooling complexity |
| Single-SPA | Route-owned app shells with browser orchestration | Routing and platform ownership |
| Subdomain per product | Fully separate products | No shared page shell; routing at DNS level |
mountly does not replace subdomain separation. If Product A and the platform are truly separate products with no shared page, deploy them under different subdomains and skip composition entirely.
Stage 1: Monorepo + shared npm libs
Section titled “Stage 1: Monorepo + shared npm libs”Best when: you can merge into one repo (or publish internal packages) and coordinate deploys for now.
Structure:
my-org/├── packages/│ ├── ui-lib/ # shared components — no mountly│ └── widgets/ # mountly widgets consuming ui-lib└── apps/ └── platform/ # host app — lazy-imports widgets- Each team owns a vertical folder with strict import boundaries.
- Use framework-native lazy routes until you need independent CDN deploys.
- Promote widgets to published packages when a second consumer appears.
Example: monorepo-component-library in the repo (docs/examples/monorepo-component-library/).
This is what Reddit commenters mean by “monorepo + lazy loaded feature modules until you are ready for a clean break.” Start here if you can.
Stage 2: Widget drop-in for foreign hosts
Section titled “Stage 2: Widget drop-in for foreign hosts”Best when: the host page is owned by another team (CMS, static HTML, legacy Rails/Django shell) and you need one or a few interactive regions that call your API.
Product A ships a widget bundle. The platform team drops a custom element — they never touch React:
<product-a-settings trigger="viewport" props='{"tenantId":"acme"}'></product-a-settings>- No Module Federation
sharedblock. - No Single-SPA shell.
- Product A deploys
dist/peer.jsto a CDN; the platform updates one URL when you release. - When Product A becomes standalone: keep the same widget artifact, swap the host page. No rewrite.
Examples:
- Platform embed scenario — Product A inside another team’s platform (mirrors the classic “should I use micro frontends?” question).
- marketing-site — dev ships widget, marketing drops HTML.
- plain-html — zero bundler on the host.
This is Stage 2, not Stage 4. Do not jump to manifest-driven multi-vertical architecture for a single customization.
Stage 3: Manifest + import map verticals
Section titled “Stage 3: Manifest + import map verticals”Best when: teams own widget verticals with an independent CDN deploy cadence and share one page shell.
- Each vertical builds with
mountlyRemote→dist/peer.js+ fragment JSON. - Host manifest pins platform deps (one React) and vertical URLs.
bootstrapMountly()ormountlyHostPluginwires the import map.- Run
mountly manifest validatein CI before deploy.
Examples:
- multi-vertical-host
- Manifest & host shells
- Micro-frontends guide (advanced — read Stage 1 and 2 first)
Key difference from Module Federation: React is shared through the import map, not a runtime shared negotiation block. The remote’s fragment auto-wires types and exposes.
Stage 3b: Iframe widgets, when the shared context is the problem
Section titled “Stage 3b: Iframe widgets, when the shared context is the problem”Best when: the reason you want a boundary is that you cannot trust what a
vertical does to the page — legacy globals, window mutation, prototype
patching, CSS written before anyone thought about isolation.
Stages 1–3 all share one JavaScript context. Shadow DOM scopes styles, but a
vertical can still reach window, register global listeners and break another
team at runtime — which quietly costs you the independent deployability the
split was for. If that describes your codebase, the browser will enforce the
boundary for you:
import { iframeFeature } from "mountly/iframe";
const billing = iframeFeature({ moduleId: "billing", src: "https://billing.acme.com/widget", title: "Billing breakdown", sandbox: "allow-scripts",});Same triggers, same lifecycle, same <mountly-feature> element. The widget runs
in its own document, so its globals and styles cannot reach the host or another
vertical.
The trade is real: each widget bootstraps its own framework, so this is slower than Stage 3 and you should not reach for it by default. mountly prefetches the frame document at preload time — hover, viewport, idle — which buys back the network half but not the runtime half.
See Iframe widgets.
When to escalate beyond mountly
Section titled “When to escalate beyond mountly”| Situation | Better fit |
|---|---|
| Route-to-app routing, staged rollouts, or runtime release coordination | Single-SPA, a vendor MFE platform, or a delivery platform with those controls |
| Fully separate products, no shared page | Subdomain per product |
| Temporary customization in a monorepo you control | Stage 1 monorepo — do not adopt MFE tooling |
| Shared global client state across every region | One app shell |
FAQ (from common objections)
Section titled “FAQ (from common objections)”“Module Federation + Vite is state of the art — should I use that?”
Section titled ““Module Federation + Vite is state of the art — should I use that?””For full app remotes with deep horizontal integration, maybe. For component-sized features dropped into an existing page, Federation adds shared block maintenance, duplicate-React risk, and build-time coupling — without giving you HTML-addressable widgets or on-demand triggers.
mountly’s Vite story (mountlyRemote + mountlyHostPlugin) gives typed native ESM imports and import-map sharing instead. See vite-host-remotes-url.
“Won’t I load duplicate React and break hooks?”
Section titled ““Won’t I load duplicate React and break hooks?””Yes — if the host bundles React and a vertical ships a self-contained dist/index.js that also bundles React. Fix:
- Use
dist/peer.json React hosts. - Pin one React version in the host import map.
- Run
mountly manifest validate(ormountly doctor) in CI.
See Version coordination.
“We’re doing this temporarily before a platform split.”
Section titled ““We’re doing this temporarily before a platform split.””Stage 2 covers this case. Ship a widget now; migrate to standalone by changing the host URL in the manifest instead of rewriting the widget. Federation infrastructure may add little value during a six-month transition.
“Do I need a platform team?”
Section titled ““Do I need a platform team?””A JSON manifest and mountly manifest validate can cover teams whose widgets have stable contracts and independent releases. Assign a platform owner when teams need route governance, staged rollouts, compatibility policy, or shared operational tooling. Team count alone does not decide this.
“What about web components as the common ground?”
Section titled ““What about web components as the common ground?””Valid for style/DOM boundaries. mountly uses custom elements as a delivery mechanism; you still author React/Vue/Svelte. You get lifecycle, triggers, and caching without hand-rolling mount/unmount for every team.
Next steps
Section titled “Next steps”- Not sure mountly fits at all? When not to use mountly
- Ready to ship a widget? Quick start
- Platform embed walkthrough: platform-embed example
- Why mountly exists: Why mountly