Skip to content

Spa Shell vs Module Federation

Here's a deep, honest, and up-to-date (2025) comparison between single-spa shell and Module Federation host β€” the two most used micro frontend orchestration approaches today.

People often ask: β€œAren’t they basically the same thing now?”

The answer is: No β€” they do very different things under the hood, even though from 10,000 feet they both solve β€œload multiple independent frontends in one page”.


Detailed Breakdown (What Actually Happens)

1. single-spa Shell (Root Config) β€” The Real Orchestrator

// This is literally all the shell does
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@org/cart",
  app: () => import("https://cart.example.com/app.js"), // any URL
  activeWhen: "/cart",
});

registerApplication({
  name: "@org/checkout",
  app: () => import("https://checkout.example.com/main.js"),
  activeWhen: (location) => location.pathname.startsWith("/checkout"),
});

start();
  • The shell is pure coordination logic.
  • It knows nothing about the internals of the MFEs.
  • It can load anything that exports the single-spa lifecycles (bootstrap, mount, unmount).

2025 evolution: Most new single-spa shells are now pure ESM + import maps, no SystemJS, no parcel bundling β†’ shell can be 10–15 kB.


2. Module Federation Host β€” The Code-Sharing Machine

// webpack.config.js of the HOST (shell)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        cart: "cart@https://cart.example.com/remoteEntry.js",
        checkout: "checkout@https://checkout.example.com/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, eager: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

Then inside your shell React component:

const Cart = React.lazy(() => import("cart/CartApp"));
const Checkout = React.lazy(() => import("checkout/CheckoutApp"));

// Webpack downloads the remoteEntry.js, reads the manifest, downloads chunks β†’ feels like local import

Here the host is also a fully-featured React app that consumes modules exposed by remotes.


Can You Combine Them? (Yes β€” and many do in 2025)

The most powerful production setup today is:

  • single-spa shell (for routing, layout, true isolation)
  • Module Federation (only inside each individual MFE for internal code sharing)

Or even:

single-spa shell loads Module Federation remotes via () => System.import('cart/remoteEntry.js')

This gives you the best of both worlds.


Real 2025 Recommendations (Who Should Use What)

Your Situation Recommended Approach Why
All teams use React + Webpack/Rspack Pure Module Federation host Fastest developer experience, great sharing
You need React + Vue + Angular in the same app single-spa shell (with import maps) Only real way to do true polyglot
You are a bank/enterprise with strict governance single-spa shell Zero version conflicts, full isolation
You want the lightest possible shell (< 20 kB) single-spa + pure ESM + import maps Proven at Zalando, DAZN, IKEA
You are starting a new greenfield React project Module Federation host 80% of new React MFEs in 2025 use this
You already have Module Federation but hitting version hell Migrate shell to single-spa, keep MF for internals Many companies did this in 2024–2025

Final Verdict (2025)

Question Answer
Are they the same? No β€” completely different mechanisms
Does one replace the other? No β€” they solve different primary problems
Which one is more popular today? Module Federation (among React teams)
Which is technically superior for true independence? single-spa
Can you use both at the same time? Yes β€” and it’s a very common production pattern

Bottom line:

  • Use Module Federation host β†’ if you are 90–100% React and want speed.
  • Use single-spa shell β†’ if you want bulletproof isolation, polyglot support, or the smallest possible orchestrator.

Most companies that outgrow the "React-only" phase eventually add a thin single-spa layer on top of their Module Federation setup.