DEV Community

Cover image for Module Federation with Vite in production: what changes
MFEOrchestrator
MFEOrchestrator

Posted on Originally published at mfe-orchestrator.dev

Module Federation with Vite in production: what changes

Module Federation started in Webpack, and for years that is where it stayed. It works on Vite now, through @module-federation/vite, and works well — but Vite's dev server and Vite's build are two different machines, and Module Federation has to satisfy both. Most production surprises live in that gap.

Does Module Federation actually work with Vite?

Yes. The plugin exposes the same concepts you already know — a host, remotes, exposes, shared dependencies — and a remote built by Vite can be consumed by a host built by Webpack, and the other way round. The container contract is what travels, not the bundler.

What differs is what happens underneath, and the honest summary is that Vite's dev server does not bundle. It serves native ES modules and transforms on demand. The build does bundle, through Rollup. So the code you exercise all day and the code you ship take different paths through the toolchain.

What breaks in production that worked in dev?

  • Dependency deduplication. In dev, the browser resolves modules and duplicates are cheap to miss. In the build, sharing is negotiated for real, and a shared library that was silently loaded twice becomes either a singleton conflict or a bundle that ships React more than once.
  • Module initialisation order. The dev server's on-demand transform can hide an import cycle that the bundled output makes fatal.
  • Missing externals. A dependency present in dev because something else pulled it in can be absent from a remote's build, and only fails when that remote is loaded — which may be a route most people never open.

None of these are Vite defects. They are the ordinary difference between resolving modules in a browser and linking them ahead of time — Module Federation simply gives that difference more surface.

How should you handle shared dependencies?

Share deliberately and share little. Everything that must be a singleton — the framework runtime, the router, whatever holds global state — should be shared and marked as such. Everything else is usually cheaper duplicated than coordinated.

The failure worth naming: a shared dependency with a loose version range across several independently released micro frontends means the singleton in production is whichever version happened to load first. That is not a configuration you chose; it is a race you are hosting.

Where do the remote URLs come from?

In most tutorials, from the bundler config — an object literal mapping each remote to a URL, committed to the host repository. It is the shortest thing that works, and it is also the decision that ends up costing the most.

Because that literal is the thing that has to change when you release. A new version of one remote means editing the host's config, rebuilding the host, and redeploying the shell — for a change that happened entirely somewhere else. It is the same trap described in the piece on composition versus deployment, and Vite does not change it either way.

The fix is not Vite-specific either: generate that map instead of writing it, from configuration fetched at runtime. The remote names stay in the build, because names are structural. The URLs do not have to.

What should you check before shipping?

  1. Build every remote and load them from a built host, not the dev server. This is the single check that catches most of the list above.
  2. Inspect the output for duplicated framework copies. If React appears twice, sharing is not doing what you think.
  3. Pin shared dependency versions across micro frontends, or accept that the singleton is decided by load order.
  4. Open every route that lazily loads a remote. A remote that is never loaded in testing is a remote that was never tested.
  5. Confirm the remote URLs are resolved at runtime, so a release does not require rebuilding the host.

Module Federation on Vite is production-ready. It is the assumption that the dev server tells you what production will do that is not.

Top comments (0)