When the checkout page of a global fashion retailer crashed after a weekend sprint, the culprit wasn’t a missing API key – it was a tangled monolith where React, Vue and Angular components lived side‑by‑side in a single bundle. The team spent two weeks untangling the mess, only to discover that the real problem was the architecture, not the code.
- Federated modules let React, Vue and Angular coexist without inflating bundle size.
- Share core libraries via a version‑controlled map; avoid duplicate runtimes.
- Use a lightweight, framework‑agnostic event bus for cross‑framework state.
- Encapsulate styles with CSS‑in‑JS or Web Components to keep UI consistent.
- Monitor performance with OpenTelemetry and a unified observability layer.
Before you start: Node 20+, npm 10, Webpack 6 (or Rspack 1), Vite 5, Nx 16 workspace, and basic familiarity with React 18, Vue 3, Angular 16.
Micro-frontends using React, Vue, and Angular in 2026: How to integrate them with federated modules
Micro-frontends using React, Vue, and Angular are integrated via federated modules (like Webpack 6 Module Federation or Vite 5 Federation) with a ‘shell’ app that orchestrates them. Key patterns include sharing core dependencies, using a framework‑agnostic event bus for state, and a shared design system via CSS‑in‑JS or Web Components for UI consistency.
Why multi‑framework micro‑frontends matter
The problem driving this architecture
Enterprises often inherit legacy portals built with Angular 12, while new product teams favor React 18 for its ecosystem, and a fast‑moving UI/UX group prefers Vue 3 for rapid prototyping. Mixing these choices in a monolithic build leads to:
- Bloated bundles – each framework ships its own runtime, inflating download size.
- Team friction – CI pipelines must accommodate divergent compilers and lint rules.
- Release risk – a change in one framework can unintentionally break another.
Beyond library choice: a platform‑level decision
Treating the UI as a platform rather than a collection of libraries shifts focus to integration instead of replacement. The platform decides how modules are discovered, shared, and rendered, allowing teams to own their preferred stack while still delivering a seamless user experience.
Core principles: key shifts in 2026
Federation goes native
Webpack 6 introduced native Module Federation APIs that expose a runtime manifest, while Vite 5’s federation plugin mirrors the same contract with zero‑config defaults. This native support means you no longer need a custom loader or wrapper library.
Build tooling convergence
Nx 16 now understands both Webpack and Vite projects in the same monorepo, letting you run nx run-many across frameworks and cache results in a shared .nx store. Rspack, the Rust‑based successor to Webpack, offers a 30 % faster compilation for federated builds when you need maximum speed.
State management becomes transport layer
Instead of coupling state to a specific framework, teams are adopting a transport‑layer state bus. Libraries like Zustand (≈ 2 KB) or a tiny custom Pub/Sub module expose get, set, and subscribe functions that any framework can consume.
The integration blueprint: step‑by‑step patterns
flowchart LR
Shell[Shell (React/Next.js)]
RemoteReact[Remote React]
RemoteVue[Remote Vue]
RemoteAngular[Remote Angular]
SharedStore[Shared State Bus]
DesignSys[Shared Design System]
Shell --> RemoteReact
Shell --> RemoteVue
Shell --> RemoteAngular
RemoteReact --> SharedStore
RemoteVue --> SharedStore
RemoteAngular --> SharedStore
RemoteReact --> DesignSys
RemoteVue --> DesignSys
RemoteAngular --> DesignSys
Pattern 1: Adaptive Module Federation (Webpack/Vite)
Expose each micro‑frontend as a remote with a module-federation.config.js (Webpack) or vite.config.ts (Vite). The shell consumes them via dynamic import() calls.
Pattern 2: Runtime “shell” with framework‑agnostic slots
The shell renders slots defined by a JSON manifest. Each slot declares the expected container ( When latency is critical, a backend service (Node 20 or Deno 2) aggregates the manifests and serves a single HTML shell that embeds the remote scripts via HTTP/3 + server push. This pattern lets you keep the front‑end agnostic while the backend guarantees version compatibility. Create a shared‑deps.ts file in the root of the Nx workspace: In each remote’s For Vite: This ensures only one copy of each library loads across the whole page, keeping the critical payload under control. Implement a tiny event bus that lives in the shell: React component usage: Vue component usage: Angular service usage: All three frameworks now read and write the same To avoid CSS bleed, adopt CSS‑in‑JS (e.g., Now any remote can use The shell owns the router (Next.js 13 for React, Nuxt 3 for Vue, Angular Universal 16 for Angular). Remote modules expose routing metadata: The shell loads the appropriate remote when the URL matches, then delegates rendering to the remote’s root component. This keeps navigation fast and SEO‑friendly because the shell can pre‑fetch the remote entry during idle time. Even with three runtimes, sharing core libraries caps the size increase. HTTP/3’s server push and asset caching let browsers keep each remote entry in the HTTP cache for up to a week, turning the 15 KB overhead into a one‑time cost per user session. Nx’s affected command ( With multiple runtimes, stack traces span different source maps. OpenTelemetry’s resource‑based grouping lets you view a request’s journey across React, Vue, and Angular spans in a single trace. “Our integration timeline dropped from three months per feature to one week after adopting a federated multi‑framework approach, with remote module caching.” – Engineering Lead, Fintech Case Study The retailer moved its product‑detail page from a monolithic Angular 12 app to three independent remotes (React for recommendations, Vue for reviews, Angular for inventory). After six weeks: A multi‑tenant SaaS combined legacy Angular admin consoles with a brand‑new React dashboard. Using Nx + Rspack: A Gartner‑based survey (re‑analysis 2025) shows 20 % of enterprise UI projects will adopt multi‑framework micro‑frontends by 2025. Organizations that embraced the pattern report a 30 % reduction in maintenance overhead after the first year, largely due to independent upgrade paths. If you anticipate adding a fourth framework (e.g., Svelte), consider publishing everything as Web Components. Modern browsers treat them as native elements, removing the need for runtime federation altogether. The upcoming ESM 2026 proposal adds import‑maps at runtime, which could let browsers resolve remote URLs without a bundler. Keep an eye on the TC39 drafts; early adapters may bypass webpack/vite entirely. Server‑side composition (using Edge Functions or Deno Deploy) can stitch HTML fragments before they reach the browser, eliminating the need for a client‑side shell in low‑interaction pages. Once the scaffold is running ( No, not necessarily. Strategic dependency sharing via federated modules, modern tree‑shaking, and leveraging HTTP/3 with intelligent caching can keep the total critical payload within 10‑15 % of a single‑framework app, dependent on component granularity. Directly, no. But you can establish a lightweight, framework‑agnostic state bus using a custom event system or libraries like Zustand (tiny & framework‑agnostic) or by building a shared state module that exposes simple get/set/subscribe functions consumable by any framework. Yes. Use Next.js 13 for the React shell, Nuxt 3 for Vue, and Angular Universal for Angular. Each remote can export an `renderToString` method that the shell calls during SSR, then streams the HTML to the client. My take: If your organization already struggles with monorepo lock‑step releases, the upfront investment in a federated shell pays off quickly. The real win isn’t the tech stack—it’s the ability to let each team ship on its own schedule without stepping on each other’s toes. For deeper insight into building a framework‑agnostic event bus, see our tutorial on Implementing a Framework‑Agnostic Event Bus. When you’re ready to squeeze every last byte, check out How to optimize Webpack bundle size for large‑scale React applications in 2026. If you found this guide helpful, drop a comment with your own integration stories, and feel free to share the article with teammates who are wrestling with legacy‑modern UI mashups. Happy federating!Pattern 3: API‑first integration with backend orchestration
Detailed integration walkthrough: React, Vue & Angular
Step 1: sharing dependencies & versioning strategy
// shared-deps.ts – Nx 16, Webpack 6
export const shared = {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
vue: { singleton: true, requiredVersion: '^3.3.0' },
'@angular/core': { singleton: true, requiredVersion: '^16.0.0' },
};webpack.config.js:// webpack.config.js – Webpack 6
const { shared } = require('../shared-deps');
module.exports = {
// ...other config
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'reactRemote',
filename: 'remoteEntry.js',
exposes: { './App': './src/App' },
shared,
}),
],
};// vite.config.ts – Vite 5
import { defineConfig } from 'vite';
import federation from '@originjs/vite-plugin-federation';
import { shared } from '../shared-deps';
export default defineConfig({
plugins: [
federation({
name: 'vueRemote',
filename: 'remoteEntry.js',
exposes: { './App': './src/App.vue' },
shared,
}),
],
});Step 2: cross‑framework communication channels
// src/stateBus.ts – Zustand 4.2.0
import { create } from 'zustand/vanilla';
type State = {
user: { id: string; name: string } | null;
setUser: (u: State['user']) => void;
};
export const stateBus = create<State>((set) => ({
user: null,
setUser: (u) => set({ user: u }),
}));// ReactRemote/App.tsx – React 18.2.0
import { useEffect } from 'react';
import { stateBus } from 'shell/stateBus';
export default function App() {
const user = stateBus.getState().user;
useEffect(() => {
const unsub = stateBus.subscribe((s) => console.log('User changed', s.user));
return unsub;
}, []);
return <div>Welcome, {user?.name ?? 'guest'} (React)</div>;
}<!-- VueRemote/App.vue – Vue 3.3.0 -->
<script setup>
import { onMounted } from 'vue';
import { stateBus } from 'shell/stateBus';
const user = stateBus.getState().user;
onMounted(() => {
stateBus.subscribe((s) => console.log('User changed', s.user));
});
</script>
<template>
<div>Welcome, {{ user?.name ?? 'guest' }} (Vue)</div>
</template>// angular-remote/state.service.ts – Angular 16
import { Injectable } from '@angular/core';
import { stateBus } from 'shell/stateBus';
@Injectable({ providedIn: 'root' })
export class StateService {
user$ = stateBus.getState().user;
setUser(u: any) {
stateBus.getState().setUser(u);
}
}user object without any framework‑specific glue.Step 3: styling & component encapsulation
@emotion/react for React, @emotion/vue for Vue, and Angular’s built‑in ViewEncapsulation). Alternatively, publish a Web Component library (Lit 3) that each remote can import:// design-system/button.ts – Lit 3
import { LitElement, html, css } from 'lit';
export class MyButton extends LitElement {
static styles = css`button{font:inherit;padding:.5rem 1rem}`;
render() { return html`<button><slot/></button>`; }
}
customElements.define('my-button', MyButton); with identical look‑and‑feel.Step 4: navigation & routing federation
// remoteManifest.json
{
"reactRemote": { "route": "/shop", "entry": "http://cdn.example.com/react/remoteEntry.js" },
"vueRemote": { "route": "/profile", "entry": "http://cdn.example.com/vue/remoteEntry.js" },
"angularRemote": { "route": "/admin", "entry": "http://cdn.example.com/angular/remoteEntry.js" }
}Performance & engineering trade‑offs
Aspect Single‑framework monolith Multi‑framework micro‑frontends Bundle size ~120 KB gzipped ~135 KB gzipped (10‑15 % increase) Team autonomy Low (one build pipeline) High (independent releases) Cacheability Whole app invalidates on any change Remote modules cache individually Observability Simple tracing Requires OpenTelemetry correlation across remotes Dev experience Unified linting Mixed linters; Nx mitigates overhead Bundle size vs. scalability analysis
Tooling & developer experience overhead
nx affected:build) runs only the changed micro‑frontend, cutting CI time by ~40 %. However, teams must learn two module federation configurations (Webpack vs. Vite) and coordinate version bumps in the shared‑deps map.Observability & debugging complexity
Real‑world case studies & new stats
Case 1: major e‑commerce platform’s migration
Case 2: SaaS consolidation project metrics
Metric Before After Avg. build time (CI) 12 min 4 min Release frequency bi‑weekly weekly Incident rate (post‑deploy) 8/month 2/month Long‑term maintenance cost data
Future‑proofing & alternative paths for 2026+
The rise of Web Components as integration layer
Speculative ECMAScript Modules integration
Backend‑driven composition alternatives
Summary: a pragmatic decision framework
The developer trinity: when to use this pattern
Situation Recommendation Legacy UI mixes Angular & React Adopt Module Federation, keep Angular as remote Multiple product teams with distinct stacks Use the shell + shared state bus Strict performance budget (<100 KB) Stick to a single framework or pure Web Components Need for fine‑grained A/B testing Federated remotes shine Getting started scaffold
# Create an Nx workspace with React, Vue, and Angular presets
npx create-nx-workspace@latest micro-platform --preset=empty
# Add apps
nx g @nrwl/react:app shell
nx g @nrwl/vue:app vueRemote
nx g @nrwl/angular:app angularRemote
# Install federation plugins
npm i -D @module-federation/webpack-module-federation-plugin@6 \
@originjs/vite-plugin-federation@1
# Generate shared-deps.ts
cat > shared-deps.ts <<'EOF'
export const shared = {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
vue: { singleton: true, requiredVersion: '^3.3.0' },
'@angular/core': { singleton: true, requiredVersion: '^16.0.0' },
};
EOFnx serve shell), open http://localhost:4200 and watch the three remotes mount into their slots.Common errors & fixes
What you see Why it happens Fix “React is undefined” in Vue remote Shared‑deps map missing react entry for Vue build.Add react to the shared object and rebuild.CSS bleed from Angular component Angular’s default ViewEncapsulation disabled. Set encapsulation: ViewEncapsulation.ShadowDom or use CSS‑in‑JS.Remote entry 404 during navigation Manifest URL points to the wrong CDN path. Verify remoteEntry.js URLs in remoteManifest.json.State updates not propagating Event bus imported from different copies of the module. Ensure the state bus lives in the shell and is imported via an absolute path. SSR page crashes on remote load Remote script expects window but runs on server.Guard remote entry with if (typeof window !== 'undefined').Frequently asked questions
Does using three frameworks triple our bundle size?
Can we share React Context or Vuex with Angular?
Is server‑side rendering possible with a mixed‑framework shell?