Micro-Frontends with React

In modern web development, Micro-Frontends is a powerful architectural pattern that allows multiple teams to work on different parts of an application independently, thus improving scalability, flexibility, and development speed.

Introduction to Micro-Frontends

What Are Micro-Frontends?

Micro-Frontends is a frontend architecture where a web application is split into smaller, self-contained units (micro-apps) that are developed, tested, and deployed independently. Each of these micro-apps is responsible for a specific feature or section of the user interface (UI). This approach is similar to Microservices on the backend but applied to the frontend layer.

Why Micro-Frontends?

As web applications grow larger and more complex, the traditional monolithic architecture can lead to issues in scalability, team collaboration, and maintainability. Micro-frontends solve these challenges by:

  • Allowing independent teams to work on different features without interfering with each other.
  • Reducing the complexity of large applications.
  • Enabling independent deployment of individual features or sections of the app.
  • Facilitating the use of different technologies or frameworks across different parts of the frontend.

Micro-Frontends vs. Monolithic Frontends

Monolithic Frontend Architecture

In a monolithic frontend, all the UI components and logic reside in a single codebase, built as one large application. This structure can become difficult to manage as the application grows, especially if multiple teams are working on it.

Problems with Monolithic Frontends:

  • Tight coupling: All components are tightly integrated, making updates more challenging.
  • Scaling issues: Scaling large teams working on the same codebase becomes difficult.
  • Deployment bottlenecks: A minor update requires redeploying the entire application.

Benefits of Micro-Frontends

  • Independent Development: Teams can develop, test, and deploy their micro-frontends independently.
  • Technology Flexibility: Teams can choose the best tools, libraries, or frameworks for their micro-frontend.
  • Faster Deployment: Small, independent units allow for faster deployment cycles.
  • Scalability: Micro-frontends allow scaling the application easily as more teams or developers can work simultaneously on different features.

Core Principles of Micro-Frontends

Micro-frontends follow several key principles to ensure consistency and maintainability:

  1. Independence: Each micro-frontend should be developed and deployed independently.
  2. Isolation: Micro-frontends should be isolated from each other, meaning they should not affect each other’s state or functionality.
  3. Routing: Each micro-frontend is responsible for its own routing and should have a way to connect to the overall application’s navigation.
  4. Composition: The main application (shell) is responsible for composing and displaying micro-frontends.
  5. Resilience: The failure of one micro-frontend should not crash the entire application.

Building Micro-Frontends in React

Approaches for Implementing Micro-Frontends

There are several ways to implement micro-frontends in React, each with different levels of complexity and benefits. These approaches include:

  • Iframe-based Micro-Frontends: Embedding micro-frontends using iframes.
  • Web Components: Using custom elements to embed micro-frontends.
  • Module Federation (Webpack 5): Sharing code between different micro-frontends using Webpack’s Module Federation.
  • Single-SPA: A framework for building micro-frontends.

Let’s dive deeper into the Webpack Module Federation and Single-SPA approaches, which are more common in React-based micro-frontend applications.

Using Webpack Module Federation

Webpack Module Federation allows you to build micro-frontends by sharing code between different applications. This approach helps load micro-frontends dynamically at runtime and share dependencies between them.

Step 1: Setting up Webpack for Module Federation

Let’s build two React micro-frontends: App 1 (host) and App 2 (remote).

App 1 (host):

				
					// webpack.config.js
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;

module.exports = {
  // Other webpack configuration
  plugins: [
    new ModuleFederationPlugin({
      name: 'App1',
      remotes: {
        App2: 'App2@http://localhost:3002/remoteEntry.js',
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

				
			

App 2 (remote):

				
					// webpack.config.js
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;

module.exports = {
  // Other webpack configuration
  plugins: [
    new ModuleFederationPlugin({
      name: 'App2',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button',
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};

				
			

In this configuration:

  • App1 is the host application that dynamically loads a component from App2.
  • App2 exposes the Button component for use in App1.

Step 2: Loading Remote Component in App 1

				
					import React, { Suspense } from 'react';

const RemoteButton = React.lazy(() => import('App2/Button'));

function App() {
  return (
    <div>
      <h1>App 1</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <RemoteButton />
      </Suspense>
    </div>
  );
}

export default App;

				
			

Output:

  • App 1 dynamically loads and displays the Button component from App 2 without requiring the button’s code during the build time.
  • This demonstrates the use of Webpack Module Federation for building micro-frontends in React.

Using Single-SPA

Single-SPA is a popular framework for building micro-frontends. It enables you to mount and unmount multiple frontend frameworks (React, Vue, Angular, etc.) on a single page.

Step 1: Installing Single-SPA

First, install the Single-SPA CLI globally:

				
					npm install -g create-single-spa

				
			

Step 2: Creating a Single-SPA React Application

Create your React micro-frontend using Single-SPA:

				
					create-single-spa

				
			

Follow the prompts to create a React-based micro-frontend.

Step 3: Registering Applications in Single-SPA

In the main application (or the root config), register your React micro-frontend.

				
					import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@my-org/app1',
  app: () => System.import('@my-org/app1'),
  activeWhen: ['/app1'],
});

registerApplication({
  name: '@my-org/app2',
  app: () => System.import('@my-org/app2'),
  activeWhen: ['/app2'],
});

start();
    
				
			

Output:

  • The registerApplication method allows you to mount and unmount React micro-frontends dynamically as users navigate through the application.
  • With Single-SPA, you can orchestrate multiple micro-frontends in a single application.

Handling Communication Between Micro-Frontends

Prop Drilling and Context API

For small-scale micro-frontends, you can pass data between different micro-frontends using props or the Context API.

Example: Sharing state via props.

				
					function Parent() {
  const [message, setMessage] = React.useState("Hello from Parent");

  return <Child message={message} />;
}

function Child({ message }) {
  return <h2>{message}</h2>;
}

				
			

Using a Global Event Bus

For larger applications, using a global event bus or shared state management (like Redux) is more efficient.

Example: Simple Event Bus.

				
					const eventBus = {
  events: {},
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(fn => fn(data));
    }
  },
  on(event, fn) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(fn);
  },
};

// Micro-frontend 1
eventBus.on('message', (data) => console.log('Micro-frontend 1 received:', data));

// Micro-frontend 2
eventBus.emit('message', 'Hello from Micro-frontend 2');

				
			

Output:

  • When Micro-frontend 2 emits a message, Micro-frontend 1 listens and responds accordingly. This demonstrates a simple event-driven communication system between micro-frontends.

Challenges and Best Practices for Micro-Frontends

Challenges

  • Performance: Loading multiple micro-frontends can lead to performance bottlenecks if not optimized.
  • Shared Dependencies: Conflicts may arise if different micro-frontends use different versions of the same library.
  • Routing Complexity: Managing routes across micro-frontends can be challenging.

Best Practices

  • Avoid Duplication: Share common libraries (like React) using tools like Webpack Module Federation.
  • Lazy Loading: Load micro-frontends lazily to improve performance.
  • Consistent UI: Use a shared design system to ensure consistency across micro-frontends.

Micro-frontends provide a scalable way to manage large frontend applications by splitting them into smaller, self-contained units. In React.js, you can implement micro-frontends using tools like Webpack Module Federation or Single-SPA, allowing you to load and manage separate applications dynamically. While micro-frontends bring many advantages in terms of scalability and team autonomy, they also present challenges such as performance and shared dependencies that must be carefully managed. Happy Coding!❤️

Table of Contents