Microfrontends With React
kpiteng
Posted on August 11, 2021
All we know about Microservices, which helps to develop, deploy and maintain applications individually. Like Uber where booking and payment services are individually developed and deployed. Same way we can develop, deploy react applications individually. It means, your application is divided into various individual applications and easy to maintain, that concept known as Micro Frontends.
Today, we will see a React Website using Micro Frontends where we create three applications, Blogs App, Header App and Container App (Which glue Blog & Header and represent a single web application).
Please download full source code from our GitHub.
Create React Applications -
Let’s create three react application,
- Blogs (Website Blogs App)
- Header (Website Header)
- Container (Actual Website, Where we merged Blog & Header)
npx create-react-app container
npx create-react-app blogs
npx create-react-app header
Blog Application -
Let’s create Constant add array of blogs,
export const arrBlogs = [
{
"blogID": 1,
"blogName": "React Navigation",
"blogDetail": "Hello Developers! Let's see what's new in React Navigation 6.x.",
"blogURL": "https://www.kpiteng.com/blogs/react-nativagation-6.x"
},
{
"blogID": 2,
"blogName": "Securing React Native Application",
"blogDetail": "Discover a ways to develop secure react native application.",
"blogURL": "https://www.kpiteng.com/blogs/securing-react-native-application"
},
{
"blogID": 3,
"blogName": "Top 10 React Tricks Every Developer Should Use",
"blogDetail": "Discover a set of React best coding practices, tips and tricks that should be known by all developers.",
"blogURL": "https://www.kpiteng.com/blogs/top-10-react-tricks-every-developer-should-use"
}
]
Let’s do code for Blogs Listing, Create a file Blog.js
import React, { useState, useEffect } from "react";
import {arrBlogs} from './Constant';
import {
Link
} from "react-router-dom";
import "./App.css";
function App() {
return (
<div className="container mt-5">
<div className="row">
{
arrBlogs.map((blog, index) => {
return (
<div className="col-xs-12 col-sm-12 col-md-6 col-lg-4 col-xl-4 mb-5">
<div className="card">
<Link to={{pathname: `/blogdetail/${blog.blogID}`, id: blog.blogID, item: blog}} >
<div class="card-body">
<h5 class="card-title">{`#${blog.blogID}`}</h5>
<p class="card-text">{blog.blogName}</p>
<p class="card-text">{blog.blogDetail}</p>
</div>
</Link>
</div>
</div>
)
})
}
</div>
</div>
);
}
export default App;
Blogs are located at url.com/blogs, So we need to set up react-router-dom and history.
yarn add react-router-dom history
To see blog detail we need to setup code for BlogDetail, create file BlogDetail.js
import React, { useState, useEffect } from "react";
import {arrBlogs} from './Constant';
import "./App.css";
function BlogDetail(props) {
const [blogDetail, setBlogDetail] = useState({});
useEffect(() => {
const blogID = parseInt(props.match.params.blogid);
const index = arrBlogs.findIndex((blog) => blog.blogID === blogID);
if (index !== -1){
setBlogDetail(arrBlogs[index])
}
}, []);
return (
<div className="container mt-5">
<div className="row">
<div className="card">
{
Object.keys(blogDetail).length > 0 && <>
<p>{`#${blogDetail.blogID}`}</p>
<p>{blogDetail.blogName}</p>
<p>{blogDetail.blogDetail}</p>
<p>{blogDetail.blogURL}</p>
</>
}
{
Object.keys(blogDetail).length === 0 &&
<p>We're sorry, Cound't find Blog</p>
}
</div>
</div>
</div>
);
}
export default BlogDetail;
Finally, We have Constant, Blogs and BlogDetail. Now Let’s do code for Blogs, BlogDetail routing. Replace App.js code with following,
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { createBrowserHistory } from "history";
import Blogs from './Blogs';
import BlogDetail from './BlogDetail';
import "./App.css";
const defaultHistory = createBrowserHistory();
function App({ history = defaultHistory }) {
return (
<Router>
<Switch>
<Route exact path="/" component={Blogs} />
<Route exact path="/blogdetail/:blogid" component={BlogDetail} />
</Switch>
</Router>
);
}
export default App;
Now, it’s time to run the application. We can see the list of Blogs and on press of the blog it redirect users to blog detail.
Header Application -
Here, We simply add header div to demonstrate Header Application. So, let’s add all required dependencies.
yarn add react-router-dom history
Let’s modify code for App.js
import React from "react";
import { createBrowserHistory } from "history";
import "./App.css";
const defaultHistory = createBrowserHistory();
function App({ history = defaultHistory }) {
return (
<div>
<p>KPITENG (Header Application)</p>
</div>
);
}
export default App;
Now, let’s run the application, It will show a Simple Header.
So, we have two applications ready, Blogs Application - where we do code for Blogs Listing, Header Application - Where we do code for Showing Header In Application.
Container Application -
Now, it's time to setup our Container Application which actually use/merge both Header and Blogs Application into our Container Application (Our Main Website)
Let’s add react-router-dom, history to Container Application. After that let’s update code for App.js
import React, { useState } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import { createBrowserHistory } from "history";
import MicroFrontend from "./MicroFrontend";
import "./App.css";
const defaultHistory = createBrowserHistory();
const {
REACT_APP_HEADER_HOST: headerHost,
REACT_APP_BLOGS_HOST: blogHost,
} = process.env;
function Header({ history }) {
return <MicroFrontend history={history} host={headerHost} name="Header" />;
}
function Blogs({ history }) {
return <MicroFrontend history={history} host={blogHost} name="Blogs" />;
}
function BlogDetail({history}) {
return (
<div>
<MicroFrontend history={history} host={blogHost} name="Blogs" />
</div>
);
}
function Home({ history }) {
return (
<div className="container">
<Header />
<Blogs />
</div>
);
}
function App() {
return (
<BrowserRouter>
<React.Fragment>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/blogdetail/:blogid" component={BlogDetail} />
</Switch>
</React.Fragment>
</BrowserRouter>
);
}
export default App;
SetUp Micro Frontends -
Think, how my Container app knows about Header Application and Blogs Application. Let’s set it up one by one.
*SetUp Web Application Port - *
Container Application - Port 3000
Header Application - Port 3001
Blogs Application - Port 3002
To do this, update package.json,
Container Application,
"scripts": {
"start": "PORT=3000 react-app-rewired start",
},
Header Application,
"scripts": {
"start": "PORT=3001 react-app-rewired start",
},
Blogs Application,
"scripts": {
"start": "PORT=3002 react-app-rewired start",
},
Now, Create .env file in root directory of Container Application,
REACT_APP_HEADER_HOST=http://localhost:3001
REACT_APP_BLOGS_HOST=http://localhost:3002
You know, React App bundle entire applications to main.js, Where we have functions to render, mount, unmount components.
Render Function Name: render{ApplicationName}
UnMount Function Name: unmount{ApplicationName}
So, Your Blogs App looks like,
renderBlogs
unmountBlogs
Same way, Header App looks like,
renderHeader
unmountHeader
Let’s create a MicroFrontend.js file in Container App, which has business logic for mount, unmount components.
import React, { useEffect } from "react";
function MicroFrontend({ name, host, history }) {
useEffect(() => {
const scriptId = `micro-frontend-script-${name}`;
const renderMicroFrontend = () => {
window[`render${name}`](`${name}-container`, history);
};
if (document.getElementById(scriptId)) {
renderMicroFrontend();
return;
}
fetch(`${host}/asset-manifest.json`)
.then((res) => res.json())
.then((manifest) => {
const script = document.createElement("script");
script.id = scriptId;
script.crossOrigin = "";
script.src = `${host}${manifest.files["main.js"]}`;
script.onload = () => {
renderMicroFrontend();
};
document.head.appendChild(script);
});
return () => {
window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`);
};
});
return <main id={`${name}-container`} />;
}
MicroFrontend.defaultProps = {
document,
window,
};
export default MicroFrontend;
As you can see MicroFrontend component will take name, host and history as params. See the fetch function which fetch the asset-manifest.json from the host and create a script object using the main.js and it will use the render function to mount components.
SetUp Micro Frontends for Blogs Application -
Let’s install react-app-rewired package which overrides the build config without ejecting the app.
yarn add react-app-rewired
Create config.overrides.js in the root directory of blogs application and add the following code.
module.exports = {
webpack: (config, env) => {
config.optimization.runtimeChunk = false;
config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};
config.output.filename = "static/js/[name].js";
config.plugins[5].options.filename = "static/css/[name].css";
config.plugins[5].options.moduleFilename = () => "static/css/main.css";
return config;
},
};
Now, let’s update scripts section of package.json file,
"scripts": {
"start": "PORT=3002 react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
And final step in Blogs Application to update index.js,
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
window.renderBlogs = (containerId, history) => {
ReactDOM.render(
<App history={history} />,
document.getElementById(containerId),
);
};
window.unmountBlogs = containerId => {
ReactDOM.unmountComponentAtNode(document.getElementById(containerId));
};
if (!document.getElementById('Blogs-container')) {
ReactDOM.render(<App />, document.getElementById('root'));
}
SetUp Microfrontends for Header Application -
- Install react-app-rewired
- Update package.json
- Update index.js file
Finally, We run the Container App (Our Main Web Application)
Please download full source code from our GitHub.
Thanks for reading Blog!
KPITENG | DIGITAL TRANSFORMATION
www.kpiteng.com/blogs | hello@kpiteng.com
Connect | Follow Us On - Linkedin | Facebook | Instagram
Posted on August 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.