Micro Frontend - A Practical Guide to Designing Scalable Systems
- Published on
- • 14 mins read

Introduction
Currently, Single Page Apps (SPAs) are extremely popular, they have many features and are also very complex, often combined with Microservices architecture at the backend layer. After a period of development, these SPAs become cumbersome, and harder to maintain, they are referred to as Frontend Monolith.
In recent years, the application of concepts from Microservices to Frontend applications has been mentioned quite frequently. The idea of Micro Frontends is to break these applications into composite parts of features, each feature can be developed by an independent team.

Software development models before the advent of Micro frontends. Source: https://micro-frontends.org/
The Monolith: a team develops all components of the product from Database, Backend, Frontend ⇒ There will be a problem when the product grows as follows:
- Not every member of the team can do both frontend and backend (There's no way a Junior developer can code React in the morning and Golang in the afternoon at a high level)
- The product is large, the amount of knowledge is vast, it will be difficult for any member to grasp and understand deeply both frontend and backend.
Front & Back: to overcome the above problem, we can split into 2 independent frontend and backend development teams. However, for large products like an e-commerce platform (Tiki, Shopee,...) that need to support functions such as products, ordering, payment, search,... the workload and knowledge will be very large and we need to apply microservices to solve these problems.
Microservices: as mentioned above, we break down functions into separate services for convenient development. However, this service division is only at the backend, so the frontend still has to develop common functions together in one source code.

Micro frontends model: each team will develop independent products (from Database, Backend to Frontend). Then integrate these independent products together into a common product. Source: https://micro-frontends.org/
When should we consider using Micro frontends
In the following cases, we can use Micro Frontends:
- A product has many functional modules and you want multiple teams to be able to develop at the same time
- Perhaps you want to develop a progressive or responsive web application but you are having difficulty integrating it into your current source code
- Maybe you want to use a new library to speed up the development process of your product (e.g.: previously used Angularjs (1.x) for development and now want to use ReactJS for development)
- You want to use a new library to support product features, such as using Webpack 5.x but the current project is using Webpack 3.x and it's hard to upgrade to Webpack 5.x because there are quite a few dependencies affected.
- Maybe you want to speed up the product development process by having different teams participate in the development of a product at the same time by splitting into multiple modules and developing independently.
And many other reasons…
Advantages and Disadvantages of Micro Frontends
Advantages

- Separates functional modules into many separate source code parts. This reduces dependencies in each project, reduces the amount of code, speeds up the build deploy process, and makes js bundle files lighter.
- Scalability is easy with multiple teams participating.
- Can use different libraries, frameworks (React, Angular) to develop different modules of a project.
- Ability to update, upgrade libraries or redevelop a part of the project.
- Easy to test functions independently.
Disadvantages
- Breaking down projects will lead to duplication of dependencies or source code.
- Many development teams make it difficult to manage source code if there are no clear common regulations from the beginning.
Some methods of implementing Micro Frontends
Build-time integration
is to treat applications as a package and the main application will add sub-applications as a library as follows:
{
"name": "@micro-frontends/container",
"version": "1.0.0",
"description": "Micro frontends demo",
"dependencies": {
"@micro-frontends/products": "^1.2.3",
"@micro-frontends/checkout": "^4.5.6",
"@micro-frontends/user-profile": "^7.8.9"
}
}
This approach has some limitations such as:
- We will have to re-compile (bundle) the main applications and release them again whenever the sub-applications change (release new version from 0.0.1 ⇒ 0.02)
- There is no synchronization of functions between the main applications if we miss the synchronization process of the sub-application version (This could also be an advantage if we do not want to upgrade the function on a certain page)
- Dependencies on each other
- If the
@micro-frontends/container
project uses React and the@micro-frontends/products
also use React, there will be a duplication of the library and an increase in the size when loading the web page - If the
@micro-frontends/container
project uses React and the@micro-frontends/products
use the same React with the main project, it will depend on the version of the main project.
- If the
Run-time integration via iframes
<html>
<head>
<title>Micro frontends</title>
</head>
<body>
<h1>Welcome to Micro frontends</h1>
<iframe id="micro-frontend-container"></iframe>
<script type="text/javascript">
const microFrontendsByRoute = {
'/': 'https://xxx.com/demo/react-example',
'/products': 'https://xxx.com/demo/react-example/products'
};
const iframe = document.getElementById('micro-frontend-container');
iframe.src = microFrontendsByRoute[window.location.pathname];
</script>
</body>
</html>
Each time the url changes from / to /products, the content of the page will be reloaded by content from a different domain, in the example it is https://xxx.com/demo/react-example/products.
Detailed demo: https://taidang.cloud/docs/example/iframes/
Advantages:
- Not affected by styles (CSS) between the main page and the page in the iframe
Limitations:
- Must reload the entire page when changing the path
- Difficulty in communicating between functions
Run-time integration via JavaScript
These approaches are where we declare global functions to support rendering functions in the sub-project. Then in the main project, we will attach the script bundle file of the sub-projects, next, if you need to display a function, just call that function.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
window.renderProducts = (containerId, history) => {
ReactDOM.render(
<App history={history} />,
document.getElementById(containerId),
);
};
Detailed demo: https://taidang.cloud/docs/example/run-time-integration/
Run-time integration via Web Components
This approach allows us to declare an HTML Custom Element, for example, we declare an HTML Custom Element then wherever we want to use it, we just need to insert the code to be able to use it.
Detailed demo: https://taidang.cloud/docs/example/web-components/
Advantages:
- No dependency on dependencies between projects (for example: different React versions between projects)
- Because it allows creating an HTML Custom Element, we can attach this Custom HTML tag to any HTML code, regardless of which frontend framework the project is using
- Supports Shadow DOM: allows independent css style, does not affect css between projects
- Can be developed in the direction of a package (publish to a registry) without needing a domain host for the project, so it is simple in managing release versions.
Limitations:
- Cannot share resources between projects (for example: using the same React library)
Module Federation Webpack 5
Module Federation is a new feature of Webpack 5. It allows us to configure so that one application can dynamically load code from another application.
Simply understood, we have 2 independently developed applications A and B, application B is a small functional part of application A. Module Federation will allow us to embed application B and application A and share resources between them.
For details, please refer to the documentation at Module Federation and examples at Module Federation Examples
Detailed demo:
Web components in React Web components in React combined with Redux
Advantages:
- Can share resources between projects. For example, project A uses React 16.x and project B also uses React 16.x, then when loading module B, there is no need to load React again, if the 2 versions are different, it will automatically load the missing React version.
- Simple communication between projects, can use a common Redux store between projects
Limitations:
- Projects must use Module Federation of Webpack 5.x
- Requires projects to have static domains to load the corresponding bundle files. Because the Module Federation functions only support configuration to load files from a remote url
Examples
Iframe
To deploy an iframe is simply straightforward. We just need to insert the HTML tag to immediately have another cool app in our application.
Demo: I will demo the iframe by attaching another app (app products showing related products) to a main app as shown below.

In the App.jsx
file of the container
project, the HTML tag iframe has been inserted as below, and the result will be as above. We have the Related products function inside the container
app.
// iframes/container/src/App.jsx
<div className="p-1" style={{width: 210}}>
<iframe className="w-full h-full" src="https://xxx.com/demo/react-example/products#/" />
</div>
Run-time integration via JavaScript
In React, the usual index.jsx
file will render into a div element with id root like
// /run-time-integration/products/src/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
We will adjust it as follows, declare a global function renderProducts passing in the parameter containerId and proceed to use ReactDOM to render into this element.
// /run-time-integration/products/src/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
window.renderProducts = (containerId, history) => {
ReactDOM.render(
<App history={history} />,
document.getElementById(containerId),
);
};
In the index.html file of the container, we load the script bundle file of the products project and then call the global function renderProducts to render the products app into the container application
<!-- /run-time-integration/container/public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>The Model Store</title>
<link href="https://unpkg.com/tailwindcss@2.2.7/dist/tailwind.min.css" rel="stylesheet">
<!-- Load file bundle of the products project -->
<script src="http://localhost:8002/main.js"></script>
</head>
<body>
<div id="root"></div>
<!-- Call the renderProducts function to render the products app into the element with id 'products' -->
<script type="text/javascript">
window.onload = () => {
window.renderProducts('products')
}
</script>
</body>
</html>
And the result will be as follows, the container application now includes the Related products part of the products application.
Web components
To declare an HTML Custom Element, we perform as follows:
// /web-components/products/src/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
class Webcomponents extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
ReactDOM.render(<App />, this);
}
}
customElements.define('web-components-products', Webcomponents);
In the index.html file of the container project, we load the bundle file of the products project
<!DOCTYPE html>
<html lang="en">
<head>
<title>The Model Store</title>
<script src="https://micro-frontends.tuando.net/demo/web-components/products/dist/main.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
Next, we attach our HTML tag to the display location in the container project
import React from 'react';
const App = () => {
return (
<>
<h1>The model store</h1>
<web-components-products />
</>
)
};
export default App;
Module Federation React
Module Federation is a plugin of webpack 5, so we proceed to configure webpack as follows:
Import ModuleFederationPlugin from webpack (note this module is only available in webpack 5 and above)
// webpack.config
const {ModuleFederationPlugin} = require('webpack').container;
Then we configure the plugin in the products project as follows
// webpack.config (products project)
plugins: [
new ModuleFederationPlugin({
name: 'Products',
library: {type: 'var', name: 'Products'},
filename: 'products.js',
exposes: {
'./App': './src/App',
},
shared: {
'react': {
eager: true,
singleton: true,
requiredVersion: dependencies.react,
},
'react-dom': {
eager: true,
singleton: true,
}
}
}),
new HTMLWebpackPlugin({
template: path.resolve('public/index.html'),
filename: './index.html',
chunksSortMode: 'none'
})
]
Next, we configure the webpack of the container project as follows
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
Products: 'Products@http://localhost:8002/products.js',
}
}),
new HTMLWebpackPlugin({
template: path.resolve('public/index.html'),
filename: './index.html',
chunksSortMode: 'none'
})
]
With the above configuration information, we note the section Products@http://localhost:8002/products.js
then Products is the value name in the configuration library: {type: 'var', name: 'Products'}
of the webpack file of the products project
And http://localhost:8002/products.js
is the domain and bundle file of the products project after it is started
Then when using, we just import it like a normal library or component
Module Federation React Redux
Similar to the Module Federation React Example, but this demo uses Redux to communicate between two apps.
Specifically, in the demo below, you can click on a product in the Related products section, and the main product in the container app will change accordingly.
First, we need to set up Redux in both the container and the products app.
In the products app:
// /products/src/index.js
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const initialState = {
product: 'Initial Product'
};
function reducer(state = initialState, action) {
switch (action.type) {
case 'CHANGE_PRODUCT':
return {
...state,
product: action.product
};
default:
return state;
}
}
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
In the container app:
// /container/src/index.js
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const initialState = {
product: 'Initial Product'
};
function reducer(state = initialState, action) {
switch (action.type) {
case 'CHANGE_PRODUCT':
return {
...state,
product: action.product
};
default:
return state;
}
}
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Then, in the products app, when a product is clicked, we dispatch an action to change the product:
// /products/src/App.js
import { useDispatch } from 'react-redux';
function App() {
const dispatch = useDispatch();
const changeProduct = (product) => {
dispatch({ type: 'CHANGE_PRODUCT', product });
};
return (
<div>
<button onClick={() => changeProduct('Product 1')}>Product 1</button>
<button onClick={() => changeProduct('Product 2')}>Product 2</button>
</div>
);
}
export default App;
In the container app, we listen for changes to the product and display the current product:
// /container/src/App.js
import { useSelector } from 'react-redux';
function App() {
const product = useSelector(state => state.product);
return (
<div>
<h1>Current product: {product}</h1>
</div>
);
}
export default App;
This way, when a product is clicked in the products app, the product in the container app will change accordingly.
Angular React Example
Integrate a project using the React library (products directory) into a project using the Angular Framework (container directory)
Container directory (Angular)
Use ng cli to create a project with the command ng new container. Next, to use Web components, we must import CUSTOM_ELEMENTS_SCHEMA from @angular/core
as follows
// angular-react/container/src/app/app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
bootstrap: [AppComponent]
})
export class AppModule { }
Next, in the file container/src/index.html
, don't forget to attach the script file of the products project
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Container</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://unpkg.com/tailwindcss@2.2.7/dist/tailwind.min.css" rel="stylesheet">
<script src="https://micro-frontends.tuando.net/demo/angular-react/products/dist/main.js"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
Next, in the file container/src/app/app.component.html
, we attach the HTML Custom Element we declared of the products project as follows
<div class="p-1">
<web-components-products></web-components-products>
</div>
That's it, we have a high-quality website using both Angular and React technologies
Note: adjust some settings in the angular.json file to serve the build and deploy process
"deployUrl": "https://xxx.com/demo/angular-react/container/dist/", # Change the domain to load static files
"index": {
"input": "src/index.html",
"output": "../../index.html" # Change the location of the index.html file after build
},
Products directory (ReactJS)
Declare an HTML Custom Element to serve the render in the container project
// /angular-react/products/src/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
class Webcomponents extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
ReactDOM.render(<App />, this);
}
}
customElements.define('web-components-products', Webcomponents);
Reference materials:
- Source code: https://github.com/dangtantaibk/micro-frontends
- https://martinfowler.com/articles/micro-frontends.html
- https://micro-frontends.org/
Happy reading