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.
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.
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:
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.
Micro-frontends follow several key principles to ensure consistency and maintainability:
There are several ways to implement micro-frontends in React, each with different levels of complexity and benefits. These approaches include:
Let’s dive deeper into the Webpack Module Federation and Single-SPA approaches, which are more common in React-based micro-frontend applications.
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.
Let’s build two React micro-frontends: App 1
(host) and App 2
(remote).
// 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 } },
}),
],
};
// 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:
Button
component for use in App1.
import React, { Suspense } from 'react';
const RemoteButton = React.lazy(() => import('App2/Button'));
function App() {
return (
App 1
Loading... }>
Button
component from App 2 without requiring the button’s code during the build time.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.
First, install the Single-SPA CLI globally:
npm install -g create-single-spa
Create your React micro-frontend using Single-SPA:
create-single-spa
Follow the prompts to create a React-based micro-frontend.
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();
registerApplication
method allows you to mount and unmount React micro-frontends dynamically as users navigate through the application.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 ;
}
function Child({ message }) {
return {message}
;
}
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');
Micro-frontend 2
emits a message, Micro-frontend 1
listens and responds accordingly. This demonstrates a simple event-driven communication system between 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!❤️