Micro-Frontends in 2026: When Module Federation Still Works — and When to Add Web Components
Module Federation is not dead. single-spa is not irrelevant. Web Components are not the silver bullet. The right choice in 2026 depends on team topology, deployment constraints, and how much framework coupling you can live with.
Micro-frontend architecture attracts more strong opinions per line of code than almost any other frontend topic. Part of the reason is that the right answer genuinely varies by context — and people share their answers without sharing their context.
Here is our context, and what we chose.
When to Consider Micro-Frontends at All
Micro-frontends solve an organizational problem more than a technical one. Before choosing an approach, ask:
- Do multiple teams own different parts of the same UI surface?
- Do those teams need to deploy independently without coordinating releases?
- Is the codebase large enough that a full build takes more than a few minutes?
If the answer to all three is no, a micro-frontend architecture adds complexity without adding value. A well-structured monorepo is almost always the simpler choice for a single team or a small number of teams with tight coordination.
Module Federation: What Still Works
Webpack Module Federation (v1 and v2) remains the most practical choice when:
- All your micro-frontends use the same framework (or compatible versions of React)
- You need to share state or context across micro-frontend boundaries
- Teams are willing to coordinate on shared dependency versions
MF v2 (the Rspack-native version) adds significant improvements: type-safe remote imports, a runtime plugin system, and dramatically faster builds via Rspack. If you are starting a new MF setup in 2026, MF v2 is the default choice.
// rspack.config.ts — host application
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
export default {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
billing: 'billing@https://billing.internal/mf-manifest.json',
analytics: 'analytics@https://analytics.internal/mf-manifest.json',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
The singleton: true constraint is critical. Without it, each remote can load its own copy of React, leading to context sharing failures that are extremely hard to debug.
When Module Federation Falls Short
MF struggles in these scenarios:
- Mixed frameworks — if one team uses React and another uses Vue or a legacy jQuery app, MF does not help with the framework boundary
- External consumption — if other companies or platforms need to embed your components, MF exposes implementation details and requires consumers to use your bundler setup
- Aggressive caching — MF remote URLs need to be cache-busted on deploy; without careful CDN configuration, users can get mismatched remote versions
Web Components as the Framework-Agnostic Boundary
Web Components (Custom Elements + Shadow DOM) are the right tool when you need a framework-agnostic integration boundary. They produce standard HTML elements that any framework — or no framework — can consume.
// Exposing a React component as a Web Component
import { createRoot } from 'react-dom/client';
import { BillingWidget } from './BillingWidget';
class BillingWidgetElement extends HTMLElement {
private root: ReturnType<typeof createRoot> | null = null;
connectedCallback() {
this.root = createRoot(this);
this.render();
}
disconnectedCallback() {
this.root?.unmount();
}
static get observedAttributes() {
return ['tenant-id', 'plan'];
}
attributeChangedCallback() {
this.render();
}
private render() {
this.root?.render(
<BillingWidget
tenantId={this.getAttribute('tenant-id') ?? ''}
plan={this.getAttribute('plan') ?? 'free'}
/>
);
}
}
customElements.define('billing-widget', BillingWidgetElement);
The consuming application (Vue, Angular, plain HTML) simply uses <billing-widget tenant-id="acme" plan="pro"></billing-widget>. No knowledge of React required.
Module Federation + Web Components: The Hybrid
The pattern we use for our most complex scenario: MF handles the module loading and React context sharing within the React ecosystem, while Web Components provide the integration surface for non-React consumers.
The React teams use MF remotes directly (full context sharing, shared state). The two legacy non-React apps in our ecosystem consume the same features as Web Components. The Web Components are themselves thin wrappers around the MF-loaded React components.
Migrating a Legacy Frontend
The most common real-world scenario is not greenfield — it is a legacy frontend that needs new features built in a modern stack without stopping the current roadmap.
Our migration pattern, which we call the "strangler fig":
- New features are built as standalone micro-frontends (MF remotes or Web Components)
- They are embedded in the legacy app at route boundaries — the legacy app owns the shell, the new apps own the content areas
- As new sections are built, old sections are retired — the legacy surface shrinks over time
- Once the legacy shell itself is the last remaining piece, it is replaced last
// Legacy app — embedding a new micro-frontend at a route
// (framework-agnostic version using Web Components)
// In legacy routing handler for /billing/*
function loadBillingSection(container: HTMLElement, tenantId: string) {
// Load the Web Component bundle once
if (!customElements.get('billing-widget')) {
const script = document.createElement('script');
script.src = 'https://billing.internal/widget.js';
script.onload = () => mountWidget(container, tenantId);
document.head.appendChild(script);
} else {
mountWidget(container, tenantId);
}
}
function mountWidget(container: HTMLElement, tenantId: string) {
container.innerHTML = `<billing-widget tenant-id="${tenantId}"></billing-widget>`;
}
Key Takeaways
- Choose micro-frontends for organizational reasons, not technical ones — monorepos are almost always simpler
- MF v2 (Rspack) is the default for same-framework teams in 2026 — faster builds, better DX
- Use
singleton: truefor shared framework dependencies — never skip this - Web Components are the right boundary when consumers are framework-agnostic or external
- The strangler fig pattern is the safest migration path — new features first, legacy shell last