React 19Micro-FrontendsModule FederationArchitectureScaling

Micro-Frontends 2.0: Scaling React Teams in 2026 🧩

P
Platform Engineer
Featured Guide 45 min read

Stop building Monoliths. Stop building Distributed Monoliths.

Micro-Frontends were supposed to save us. Instead, early implementations (Iframe-based, or build-time npm package aggregation) gave us version mismatches, 10MB bundles (React loaded 5 times), and CSS conflicts.

Micro-Frontends 2.0 changes everything. With Module Federation 2.0 and Rspack, we can share dependencies intelligently at runtime, allowing separate teams to deploy independently while maintaining a cohesive, high-performance application.

02. Module Federation 2.0

Federation allows a JavaScript application to dynamically load code from another application—at runtime. Unlike npm packages, which are baked in at build time, Federated Modules are "Live." If the Checkout Team deploys a fix to the CartWidget, the Main App gets that fix instantly on the next refresh, without rebuilding.

MFE 1.0 (Iframe Hell)

Complete isolation. Zero sharing. Terrible performance. Hard to deep link. Accessibility nightmares.

MFE 2.0 (Federation)

Runtime imports. Singleton React instance. Shared Context. It feels like a Monolith to the user, but deploys like Microservices to the dev.

// rspack.config.js (The Magic)
new ModuleFederationPlugin({
  name: 'shop_app',
  filename: 'remoteEntry.js',
  exposes: {
    './CartWidget': './src/components/CartWidget',
  },
  // SINGLETON: Ensures only one copy of React is loaded
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } }
});

03. Compiler Synergy

Most people don't know this: The React Compiler aims for stable references. When App A imports a Component from App B via Federation, it is technically an "External Component."

The Compiler is smart enough to treat these Federeated Modules as stable dependency roots. If the props passed to the remote component haven't changed, the Host Application will NOT re-render the wrapper, preserving the efficient update cycle even across network boundaries.

04. Shared State Strategy

How do you share state (like "User Logged In") between Micro-Frontends without coupling them tightly?

Pattern A: Custom Events

Dispatch window.dispatchEvent events. Simple, decoupled, but hard to type-check.

Pattern B: Federated Atom (Zustand)

Expose a store.js from the Host App. Remote Apps import this store and useStore(). Because React is a singleton, the specialized hooks work across the boundary.

06. Federated Component Simulator

Below simulates a "Host Application" loading a "Remote Cart Widget" from a different server URL. Notice how the state (cart count) is maintained inside the remote widget.

Interactive Playground

import React, { useState, useEffect } from 'react';

// 🧩 Micro-Frontend Simulator

export default function MFEDemo() {
    const [remoteStatus, setRemoteStatus] = useState('idle'); // idle, loading, ready
    const [cartCount, setCartCount] = useState(0);
    const [hostVersion, setHostVersion] = useState('1.0.0');

    const loadRemote = () => {
        setRemoteStatus('loading');
        // Simulate network fetch of remoteEntry.js
        setTimeout(() => {
            setRemoteStatus('ready');
        }, 1200); 
    };

    return (
        <div className="bg-slate-100 dark:bg-slate-900 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-2xl relative overflow-hidden min-h-[600px] flex flex-col">
            
            {/* Host Header */}
            <div className="bg-white dark:bg-black p-6 rounded-2xl shadow-sm mb-8 border border-slate-200 dark:border-slate-800 relative z-10">
                <div className="flex justify-between items-center mb-4">
                    <div className="flex items-center gap-3">
                        <div className="bg-blue-500 text-white p-2 rounded-lg">
                            <span className="text-xl">🌐</span>
                        </div>
                        <div>
                            <h3 className="text-xl font-bold text-gray-900 dark:text-white">Host Application (Main)</h3>
                            <p className="text-xs text-gray-500 uppercase font-bold">Port 3000 • Team Platform</p>
                        </div>
                    </div>
                     <div className="text-xs font-mono text-gray-400 border border-gray-200 dark:border-gray-800 px-2 py-1 rounded">
                        React 19.0.0 (Singleton)
                    </div>
                </div>
                
                <div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-xl border border-dashed border-gray-300 dark:border-gray-700">
                    <h4 className="font-bold text-sm mb-2 text-gray-500">Host Layout Area</h4>
                    <p className="text-sm">This content is rendered by the main application. It owns the routing and the base layout.</p>
                </div>
            </div>

            {/* Federation Zone */}
            <div className="flex-1 border-4 border-indigo-500/20 border-dashed rounded-3xl p-8 relative flex items-center justify-center bg-indigo-50/50 dark:bg-indigo-900/10 transition-all">
                <div className="absolute top-4 left-4 flex gap-2">
                     <span className="text-xs font-bold text-indigo-500 uppercase bg-white dark:bg-black px-2 py-1 rounded shadow-sm border border-indigo-100 dark:border-indigo-900">
                        Federation Slot
                    </span>
                    {remoteStatus === 'ready' && <span className="text-xs font-bold text-green-500 uppercase bg-white dark:bg-black px-2 py-1 rounded shadow-sm border border-green-100 dark:border-green-900 flex items-center gap-1"><span></span> Live</span>}
                </div>

                {remoteStatus === 'idle' && (
                    <div className="text-center animate-in fade-in slide-in-from-bottom-4">
                        <button 
                            onClick={loadRemote}
                            className="bg-indigo-600 hover:bg-indigo-500 text-white font-bold px-8 py-4 rounded-xl shadow-xl shadow-indigo-500/20 transition-transform active:scale-95 flex items-center gap-2 mx-auto"
                        >
                            <span>☁️</span> Load Remote Widget
                        </button>
                        <p className="mt-4 text-sm text-gray-500 max-w-xs mx-auto">
                            Click to dynamically fetch bundle `http://localhost:3001/remoteEntry.js`
                        </p>
                    </div>
                )}

                {remoteStatus === 'loading' && (
                    <div className="flex flex-col items-center gap-4 animate-pulse">
                         <div className="w-16 h-16 bg-indigo-200 dark:bg-indigo-800 rounded-full"></div>
                         <div className="h-4 w-32 bg-indigo-200 dark:bg-indigo-800 rounded"></div>
                         <p className="text-xs font-mono text-indigo-500">Resolving Shared Dependencies...</p>
                    </div>
                )}

                {remoteStatus === 'ready' && (
                    <RemoteWidget cartCount={cartCount} setCartCount={setCartCount} />
                )}
            </div>

        </div>
    );
}

// Simulated Remote Component
// In a real app, this file lives in a different Repo and is deployed separately!
function RemoteWidget({ cartCount, setCartCount }) {
    return (
        <div className="bg-gradient-to-br from-pink-500 to-rose-600 text-white p-8 rounded-2xl shadow-2xl transform transition-all animate-in zoom-in-90 duration-500 w-full max-w-sm border-2 border-white/20">
            <div className="flex justify-between items-start mb-6">
                <div>
                    <h4 className="text-2xl font-bold flex items-center gap-2">
                        <span>📦</span> Cart Widget
                    </h4>
                    <p className="text-xs text-white/70 uppercase font-bold mt-1">
                        Origin: Port 3001 • Team Checkout
                    </p>
                </div>
                <span className="bg-white/20 px-3 py-1 rounded-full text-sm font-bold backdrop-blur-sm border border-white/30">
                     v2.1.0
                </span>
            </div>

            <div className="bg-black/20 rounded-xl p-4 mb-6 backdrop-blur-md border border-white/10">
                 <div className="text-sm font-medium text-white/80 mb-1">Items in Cart</div>
                 <div className="text-4xl font-black">{cartCount}</div>
            </div>

            <button 
                onClick={() => setCartCount(c => c + 1)}
                className="w-full bg-white text-rose-600 font-bold py-3 rounded-lg hover:bg-white/90 transition-colors shadow-lg active:translate-y-0.5"
            >
                Add Item (Remote Action)
            </button>
            <p className="text-[10px] text-center mt-4 text-white/60">
                This component is NOT in the Host bundle. It was loaded at runtime.
            </p>
        </div>
    )
}