DEV Community

Cover image for Microfrontends With React
kpiteng
kpiteng

Posted on

Microfrontends With React

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
Enter fullscreen mode Exit fullscreen mode

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"
    }
  ] 
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Blogs are located at url.com/blogs, So we need to set up react-router-dom and history.

yarn add react-router-dom history
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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",
 },
Enter fullscreen mode Exit fullscreen mode

Header Application,

"scripts": {
   "start": "PORT=3001 react-app-rewired start",
 },
Enter fullscreen mode Exit fullscreen mode

Blogs Application,

"scripts": {
   "start": "PORT=3002 react-app-rewired start",
 },
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

So, Your Blogs App looks like,

renderBlogs
unmountBlogs
Enter fullscreen mode Exit fullscreen mode

Same way, Header App looks like,

renderHeader
unmountHeader
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
   },
 };
Enter fullscreen mode Exit fullscreen mode

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"
 },
Enter fullscreen mode Exit fullscreen mode

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'));
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (23)

Collapse
 
iamrommel profile image
Rommel C. Manalo

How do you deal with talking for each sub-frontend. If I want to pass state to another sub-frontend. Putting all the states to the main/parent application will be bad design i guess, and it will not be scalable.

Collapse
 
jennyphan profile image
Jenny Phan

We are currently using Micro Front-Ends for our application. It makes sense when you have a large application that accesses different domains. Each Micro Front-End is it's own independent team and the team is cross-functional. I would try not to share state between the Micro Front-Ends, but if you need to pass data from one to another, I would pass the least amount of data possible and let the retrieving Micro Front-End get it's own data. If you are sharing state between all Micro Front-Ends, you could use session storage but again I would limit the data being stored/passed.

Collapse
 
r4e profile image
Richie Permana

Awesome.
What is this use case for this kind of solution?

Collapse
 
kpiteng profile image
kpiteng

Consider you have own Website, having Home, About, Contact Us, Blogs, Service Section.

Instead of keeping all modules in one react application, Create Separate Applications and Merge into a Main Application.

Here, You have Blogs Section, You can create Separate React Application for Blogs and Use it inside main React Application.

Collapse
 
simbo1905 profile image
Simon Massey

Look at the introduction chapter of the book “Microfrontends In Action”. It is about independence teams building independent microfrontends that are composed into one UX for the users. So it’s about scaling up to many semi-autonomous teams who can deploy independently without a big integration of many developers into a “mega-app” which is hard “at scale”.

Collapse
 
ameybhavsar profile image
Amey Bhavsar

That's pretty cool. I'm curious about the performance advantages using micro frontends.

Collapse
 
m4rcoperuano profile image
Marco Ledesma

One of the best use cases is if you have an application with multiple modules. For example: an application that deals with invoicing may also have receipts, modifying an invoice, and more. All that can be contained in a frontend. Later, you may want to support reporting. Reporting may require multiple pages of its own. For reporting, I would consider a separate front end, just to keep the context of the code small. Interesting pattern

Collapse
 
ashishjshetty profile image
Ashish J shetty

Is this approach better than the module federation approach ? especially considering that using the module federation approach we can share the common npm packages when running the apps within the container thus reducing the bundle size.

Collapse
 
ashishjshetty profile image
Ashish J shetty

@kpiteng is this approach better than the web pack module federation approach ?
also in Module federation approach we can kind of can share the the common npm packages without increasing the bundle size. is it possible via this approach ?.

Collapse
 
bhagat profile image
Veldurthi1996

Why can't I use props children in micro frontend, I'm moving my entire react application to micro frontend, I'm using props children in few places mostly when I'm using useContext(), but seems like I'm unable to access children in props

Collapse
 
simbo1905 profile image
Simon Massey

Take a look at the book “Microfrontends In Action” that discusses exchanging state.

Essential the approach doesn’t come “for free” in terms that “fragments” as the book calls them are more “isolated” from each other than a normal app. So two things come into play: if they are closely related they should probably be in the same “frontend”. If they are truly two separate “fragments” that should be owned by different dev teams and deployed independently (which is the whole point of doing microfrontends) then you need to look at specific details of what to send “events” between the fragments that they can respond to.

I am currently looking at what the different options are for that. In some of our cases sending events via the backend makes sense for a few of our use cases. Just because the reason we need to update many fragments on the UI is exactly because the uses have pushing work to the server.

In other cases it’s is truly the case that we want to sync state across the fragments without any writes to the server. In which case the book discusses that.

Collapse
 
tonyftannous profile image
TonyFTannous

Hi. can i use lazy load, in a microfrontend app. why i ask this question because one of the rule of the microfrontend app is: when the container app tries to load the MFE, everything should be in one JS file, so we must disable the chunking. if this is true how the lazy load work??? if the chunking file will be disabled and everything should be in one JS

Collapse
 
vickywar profile image
vickywar

but isn't this what the code splitting of react does ? with the use of Suspense and lazy load. the bundle size reduces anyways .. what is the exception in this method of implementation when compared to code split ?