Microfrontends in React: A Comprehensive Guide with Example

Microfrontends in React: A Comprehensive Guide with Example

We focused on microservices in our previous blog. In this blog we will deep dive into microfrontends. In recent years, the concept of microfrontends has gained significant traction in the world of web development. As applications grow in complexity, the need for scalable, maintainable, and modular architectures becomes paramount. Enter Microfrontends, an architectural pattern that allows teams to build independent, self-contained frontend modules that integrate seamlessly. Inspired by microservices, microfrontends enable organizations to break large frontend applications into smaller, manageable pieces, promoting autonomy and scalability.

What Are Microfrontends?

Microfrontends are an architectural pattern that extends the principles of microservices to the frontend. Just as microservices break down backend applications into smaller, independent services, microfrontends decompose the frontend into smaller, self-contained units. These units(modules) are later composed to form the complete frontend application.

Key Characteristics of Microfrontends:

  1. Technology Agnostic: Different teams can use different frontend frameworks (React, Vue, Angular, etc.) for different modules.
  2. Independent Development and Deployment: Teams can work on different microfrontends simultaneously, enabling faster development cycles and reducing dependencies.
  3. Scalability: Large applications can be scaled easily by dividing them into smaller, manageable parts.
  4. Team Autonomy: Teams can work independently on different microfrontends without dependencies slowing them down.
  5. Improved Maintainability: Smaller codebases are easier to maintain than a large monolithic frontend.
  6. Fault Isolation: Issues in one microfrontend are less likely to impact the entire application, improving overall stability.

Approaches to Implement Microfrontends in React

There are several approaches to implementing microfrontends in a React application. Below are some common methods:

1. Build-Time Integration

In this approach, multiple teams build separate frontend components, and these components are integrated into a single application at build time. This method is simple but lacks runtime flexibility.

Example: Using monorepos like Nx to manage microfrontends in a single repository.

2. Run-Time Integration (Module Federation)

With Webpack 5’s Module Federation, microfrontends can be loaded dynamically at runtime, making them independent and deployable separately.

Example: Using Webpack’s Module Federation to share React components across different microfrontends.

3. Iframe-Based Microfrontends

Each microfrontend is loaded inside an iframe, ensuring complete isolation. However, communication between microfrontends becomes complex.

Example: Loading a dashboard microfrontend inside an iframe from a different domain.


When to Use Microfrontends

While microfrontends offer numerous benefits, they are not a one-size-fits-all solution. They are particularly useful in the following scenarios:

  • Large Teams: When multiple teams are working on the same application, microfrontends can help reduce coordination overhead.
  • Complex Applications: For applications with multiple features or modules, microfrontends can simplify development and maintenance.
  • Legacy Codebases: Microfrontends can be used to incrementally modernize legacy applications by replacing parts of the UI with new, independent microfrontends.

Implementing Microfrontends in React using Webpack Module Federation

Step 1: Set Up the Host and Remote Applications

We will create a Host Application that loads a Remote Microfrontend dynamically using Module Federation.

Step 2: Create the Remote Microfrontend

Install Webpack and dependencies


mkdir microfrontend-remote && cd microfrontend-remote
npm init -y
npm install webpack webpack-cli webpack-dev-server react react-dom

Configure Webpack (webpack.config.js)


const { ModuleFederationPlugin } = require("webpack").container;
const path = require("path");

module.exports = {
  entry: "./src/index",
  mode: "development",
  devServer: {
    port: 3001,
  },
  output: {
    publicPath: "<http://localhost:3001/>",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "RemoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Dashboard": "./src/Dashboard",
      },
      shared: ["react", "react-dom", "react-router-dom"],
    }),
  ],
  resolve: {
    extensions: [".js", ".jsx"],
  },
};

Create a Simple Dashboard Component (src/Dashboard.js)


import React from "react";
import { BrowserRouter as Router, Route, Link, Routes } from "react-router-dom";

const Dashboard = () => {
  return (
    <Router>
      <div>
        <h2>Microfrontend Dashboard</h2>
        <nav>
          <ul>
            <li><Link to="/analytics">Analytics</Link></li>
            <li><Link to="/reports">Reports</Link></li>
          </ul>
        </nav>
        <Routes>
          <Route path="/analytics" element={<h3>Analytics Page</h3>} />
          <Route path="/reports" element={<h3>Reports Page</h3>} />
        </Routes>
      </div>
    </Router>
  );
};
export default Dashboard;

Step 3: Create the Host Application

Install Dependencies


mkdir microfrontend-host && cd microfrontend-host
npm init -y
npm install webpack webpack-cli webpack-dev-server react react-dom

Configure Webpack (webpack.config.js)


const { ModuleFederationPlugin } = require("webpack").container;
const path = require("path");

module.exports = {
  entry: "./src/index",
  mode: "development",
  devServer: {
    port: 3000,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "HostApp",
      remotes: {
        RemoteApp: "RemoteApp@<http://localhost:3001/remoteEntry.js>",
      },
      shared: ["react", "react-dom", "react-router-dom"],
    }),
  ],
  resolve: {
    extensions: [".js", ".jsx"],
  },
};

Import the Remote Dashboard Component (src/App.js)


import React from "react";
const RemoteDashboard = React.lazy(() => import("RemoteApp/Dashboard"));

const App = () => {
  return (
    <div>
      <h1>Microfrontend Host App</h1>
      <React.Suspense fallback={<div>Loading...</div>}>
        <RemoteDashboard />
      </React.Suspense>
    </div>
  );
};
export default App;

Step 4: Run Both Applications

Start both applications in separate terminals:


cd microfrontend-remote && npm start
cd ../microfrontend-host && npm start

Now, the Host App (port 3000) will dynamically load the Dashboard from the Remote App (port 3001)!


Best Practices for Microfrontends

While the example above provides a basic implementation of microfrontends, there are several best practices to keep in mind when adopting this architecture:

  1. Consistent Design System: Ensure that all microfrontends adhere to a consistent design system to maintain a cohesive user experience.
  2. Shared Dependencies: Use Webpack’s Module Federation to share common dependencies (e.g., React, React DOM) between microfrontends to reduce bundle size.
  3. API Gateway: Implement an API gateway to manage communication between microfrontends and backend services.
  4. Versioning: Use versioning to manage updates to microfrontends and ensure compatibility with the container application.
  5. Testing: Implement end-to-end testing to ensure that microfrontends work together seamlessly.

Microfrontends offer a powerful way to build scalable, maintainable, and modular frontend applications. By breaking down monolithic applications into smaller, independent units, teams can work more efficiently and deliver features faster. By leveraging Webpack’s Module Federation, teams can seamlessly integrate microfrontends while maintaining autonomy. However, microfrontends are not a one-size-fits-all solution and should be used when the benefits outweigh the complexity.