Marcio Cunha

How to build resilient micro-frontends with Module Federation

Learn how to isolate failures and securely share dependencies between independent web applications using Module Federation in daily practice.

Marcio Cunha•5 min
Also available in:PortuguêsEspañol
Summary
  • Module Federation allows loading code at runtime between completely separate applications.
  • Fault isolation prevents the crash of a single micro-frontend from bringing down the entire main UI.
  • Proper shared dependency configuration dramatically reduces the size of downloaded software packages.
  • Fallback strategies ensure users see alternative content if a remote server fails.
  • Modular evolution requires clear contract governance and rigorous package versioning.

What micro-frontends are and why Module Federation changed the game

Splitting giant software systems into smaller parts makes life much easier for engineering teams who need to ship code concurrently without stepping on each other's toes. In web engineering, we call this approach micro-frontends, which simply means slicing a massive webpage into independent pieces. In practice, this means the payments team can update the checkout flow without needing to touch or recompile the homepage code created by another group. To make this architecture work smoothly without freezing navigation, modern tools have become essential for stitching these pieces together right inside the user's browser. Just like we discussed when exploring how to migrate a monolith to microservices smoothly, the key lies in slicing boundaries intelligently.

Historically, stitching code snippets from different projects onto the same screen was a messy logistical nightmare full of clumsy iframes and rigid script tags. The true revolution arrived with Module Federation, a technology that lets a web application import parts of another application directly at runtime, as if it were pulling a local file. In practice, the browser downloads the primary code and, when it needs an external component, dynamically fetches the required snippet straight from the remote team's server. This eliminates the need to rebuild and redeploy the entire system just because a button changed color in an isolated module.

How to isolate failures so the system never crashes entirely

When we distribute a website's logic across multiple independent servers, we introduce an invisible risk: if the server hosting the promotions module goes down, the entire site could freeze on a terrifying blank screen. To prevent a partial outage from destroying the whole user experience, we must design protective barriers based on resilience components, ensuring the rest of the page keeps running normally. In practice, this looks a lot like how to design a fault-tolerant distributed system on the backend, where every piece must assume the others will fail sooner or later.

The primary weapon to contain these damages is the use of Error Boundaries combined with safe dynamic imports. When the browser attempts to fetch a federated module and the remote server fails to respond, the error boundary intercepts the issue before it contaminates the rest of the component tree. Instead of breaking the whole page, the system displays a friendly fallback notice or hides the unavailable section. This approach protects users from frustration and gives the engineering team time to fix the issue on the remote server without panic.

Sharing dependencies securely without duplicating code

One of the biggest hazards when combining modules from different teams is bloating the browser with duplicate libraries, such as downloading three distinct versions of the same user interface library. Module Federation solves this by allowing developers to declare shared dependencies, meaning the system explicitly states: 'if the host application has already loaded React, the child modules must use that exact same instance'. In practice, this saves user bandwidth and speeds up page loading dramatically.

However, sharing dependencies requires clear semantic versioning rules to prevent silent code conflicts. If the host application runs React 18 and a remote module demands exclusive features from React 19, the system must negotiate behavior or isolate scopes to prevent bizarre runtime execution errors. To ensure this rendering dynamic does not overwhelm the browser, it is worth checking strategies like how to use Web Workers to fix render bottlenecks and keep 60 FPS during heavy screen interactions.

Implementing a basic Module Federation setup

To get our hands dirty, let's configure a simple project using the Webpack bundler, which has native support for this architecture. We need to define two primary roles: the Host container that consumes the pieces, and the Remote module that produces the shared component. Below is a practical configuration example in the remote module configuration file:

const { ModuleFederationPlugin } = require('webpack').container;\n\nmodule.exports = {\n  // ... other webpack configurations\n  plugins: [\n    new ModuleFederationPlugin({
      name: 'product_catalog',
      filename: 'remoteEntry.js',
      exposes: {\n        './ProductCard': './src/components/ProductCard',\n      },\n      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },\n    }),\n  ],\n};

In the code above, the exposes property tells the outside world which files are cleared for external importing, while the shared block ensures React is shared as a single instance. On the other end, in the main application consuming this component, we configure the plugin specifying where to locate the remote file. This symbiosis allows autonomous teams to update their products completely independently without breaking the company's core engine.

Best practices for governance and long-term stability

Keeping a distributed architecture running smoothly over the years requires rigorous discipline and clear agreements between development teams. A common mistake is allowing remote modules to create deep couplings with internal rules of the host application, destroying the advantage of independence. Each micro-frontend must act as a black box with well-defined input and output contracts, using only standardized properties and events to communicate with the rest of the page.

It is also worth investing in automated tests that simulate network failures on remote servers during continuous integration pipelines. Frontend error monitoring tools should be configured to trace exactly which federated module generated an exception, isolating the impact by responsible team. With these precautions, your architecture gains the maturity needed to scale safely.

Final thoughts on resilience in distributed architectures

Adopting micro-frontends with Module Federation brings unmatched flexibility to organizations scaling software components with dozens of simultaneous developers. However, this operational freedom comes at the cost of requiring greater architectural maturity and obsessive focus on network resilience. By intelligently isolating failures and judiciously sharing dependencies, you ensure the system withstands partial outages without punishing the end user with broken screens. At the end of the day, the best architecture is not the one that never fails, but the one that knows how to fail small and stay standing.