How to Share Code Between Micro Frontends

Sharing context, functionality, and styles between micro frontends

Fernando Doglio
Bits and Pieces
Published in
10 min readMar 21, 2024

--

The biggest benefit of micro frontends, if you don’t know about them, is that you can use them as building blocks for different projects, keeping a consistent look and user experience across all your applications.

In theory, that sounds great, but then again, building such a “technological wonder” is not trivial. If you’re building multiple projects with them and trying to get the most out of your micro frontends, then you’ll start running into a very common problem: repeated code.

Granted, more like a problem is, this is the perfect opportunity for you to abstract and share code across multiple individual micro frontends. But how can you do it?

In this article, I’m going to show you how you can use Bit to achieve exactly this!

The example project

https://bit.cloud/deletemangroup/microfe

For this example, we’ll be using Bit. And if you don’t know what that is, Bit offers tools for component-driven development, source code control, versioning, dependency management, packaging, distribution, sharing, collaboration, discoverability, search, consumption, installation, and integration. The concept of “component” within Bit encapsulates code, tests, and documentation, making it suitable for shared code among micro frontends

To illustrate how everything works, I’m going to use the following project where I have 2 working micro frontends (bit applications) that have code that could be shared instead of repeated in their individual code bases.

So for this example I’ll be using 2 micro frontends I’ve created using Bit, they’re actually Bit component applications, which means they can be deployed and executed individually. I’ll show you their code and how they share a single component that could be re-used, if refactored properly.

In the end, using Bit, I will show you how to refactor that component, extract it into an individual Bit component, and modify the other two micro frontends to use the common code instead of repeating logic.

Reviewing the micro frontends

So let’s start by reviewing our micro frontends and understanding where the repeated code/logic is. We have two micro frontends:

  • my-docs: This little micro frontend acts as a small documentation site. It lets you navigate between different sections of the documentation.
  • my-blog: This micro frontend acts as a quick and dirty blog, which has 3 different articles inside and also lets you navigate between the different pages using a menu.

The documentation site

The demo app

This application shows a navigation bar at the top, and then renders an internal page. Here’s the code for it:

import React from 'react';
import { Routes, Route} from 'react-router-dom';
import Home from './Home';
import GettingStarted from './GettingStarted';
import APIReference from './ApiReference';
import './docs.module.css'

export function Documentation() {

let prefix = ""

const defaultRoutes = [
{
path: prefix + "/getting-started",
element: <GettingStarted />
},
{
path: prefix + "/api-reference",
element: <APIReference />
},
{
path: prefix + "/",
element: <Home />
}
]
return (
<Routes>{
defaultRoutes.map( (r, index) => {
return (<Route key={index} path={r.path} element={r.element} />);
})
}
</Routes>
);
}

As you can see, the code is using react-router-dom to navigate between pages. And the code for the nav bar at the top, is the following:

import React from 'react';
import { Link, useLocation } from 'react-router-dom';

export function Nav() {
let location = useLocation()
let pathParts = location.pathname.split("/")
let usePrefix = pathParts[1].indexOf("docs") != -1
let prefix = ""

if(usePrefix) {
let pathParts = location.pathname.split("/")
prefix = "/" + pathParts[1]
}
return (
<div>
<nav>
<ul>
<li>
<Link to={ prefix + "/"}>Home</Link>
</li>
<li>
<Link to={ prefix + "/getting-started"}>Getting Started</Link>
</li>
<li>
<Link to= {prefix + "/api-reference"}>API Reference</Link>
</li>
</ul>
</nav>
</div>
)
}

Granted, the links are hard-coded, but you get the point.

Let’s now take a look at the next micro frontend.

The blog

In the case of this micro frontend, we have yet another top navigation (one that has no styles), and then we can see the content of each post underneath the menu.

Pretty basic stuff, but if you notice, the navigation logic is the same as before. Let’s take a look at the code now.

import React from 'react';
import {Routes, Route} from 'react-router-dom'
import Post1 from './posts/post1'
import Post2 from './posts/post2'
import Post3 from './posts/post3'

const posts = [
{ url: '/posts/1', component: <Post1 />},
{ url: '/posts/2', component: <Post2 />},
{ url: '/posts/3', component: <Post3 />},
];

export function BlogComponent() {
return (
<Routes>
{posts.map( (post, index) => (
<Route key={index} path={post.url} element={post.component} />
))}
</Routes>
);
}

As you can appreciate, the code for this one is very similar to the other. It’s essentially listing a set of routes and associating different components to these routes. Each of these internal components (the posts), uses the Nav component, which looks like this:

import React from 'react';
import { Link, useLocation } from 'react-router-dom';

export function Nav() {
let location = useLocation()
let pathParts = location.pathname.split("/")
let usePrefix = pathParts[1].indexOf("docs") != -1
let prefix = ""

if(usePrefix) {
let pathParts = location.pathname.split("/")
prefix = "/" + pathParts[1]
}

return (
<div>
<nav>
<ul>
<li>
<Link to={ prefix + "/posts/1"}>Post1</Link>
</li>
<li>
<Link to={ prefix + "/posts/2"}>Post2</Link>
</li>
<li>
<Link to= {prefix + "/posts/3"}>Post3</Link>
</li>
</ul>
</nav>

</div>
)
}

In case you haven’t noticed, this is the exact same code as with the other navigation menu, but it has 3 different links.

So now we understand the problem: the Nav component can be shared between both micro frontends.

Let’s see how we can do that.

The solution

Here’s what we need to do:

  1. We need to access the code of both micro frontends.
  2. Create a Bit component containing the code of the Nav component, refactored so that it can be used in different places.
  3. Export the component so that it can be re-used everywhere.
  4. Install the component in both micro frontends and publish their new version.

Easy right? Let’s get started!

Accessing the code of both micro frontends

Given how both micro frontends are Bit components, you can install them through NPM in any application. However, in that situation, they would be downloaded into your node_modules folder, and you wouldn't have access to their code.

So, instead, we’re going to use Bit to import the components into an empty project. The bit import command will do two things for us:

  1. It’ll download the code of our micro frontends into a folder at the root of the project.
  2. It’ll create symlinks inside the node_modules folder so that if you try to use any of them inside another project as a normal dependency, you'll still be able to import it as any other regular dependency.

Now, use the following commands to get you going:

mkdir new-project && cd new-project
bit init
bit import deletemangroup.microfe/apps/my-docs
bit import deletemangroup.microfe/apps/my-blog

After running these commands, you’ll have the following folder structure:

We can now start editing one of the versions of theNav component from one of the micro frontends. Let's pick the version with styles from themy-docs micro frontend.

But first, we’re going to create a new component, which is where we’re going to put our code:

bit create react navigation/topbar

At this stage, we have two micro frontends and an empty component. If we run bit start at the root of our folder, we'll get the dev server, which will let us test everything we have:

Let’s now perform the code updates on the component we want.

Let’s edit the topbar.tsx file inside our new component and add the following code:

import React, { ReactElement } from 'react';
import { Link, useLocation } from 'react-router-dom';

export type TopbarProps = [
{
url: string,
text: string
}
]

export function Topbar({routes}: { routes: TopbarProps}) {
let location = useLocation()

let pathParts = location.pathname.split("/")
let usePrefix = pathParts[1].indexOf("docs") != -1
let prefix = ""

if(usePrefix) {
let pathParts = location.pathname.split("/")
prefix = "/" + pathParts[1]
}

return (
<div>
<nav>
<ul>
{ routes.map( r => (
<li>
<Link to={r.url}>{r.text}</Link>
</li>
))}
</ul>
</nav>
</div>
)
}

This code is now accepting the list of routes as a mandatory prop, and it’s also exporting the type definition for the format of the routes array, so we make sure we know exactly how to create it.

With this change, we can now go to our my-docs component (inside the same project) and edit the internal pages of the documentation.

For example, the “Home” section looked like this before:

import React from 'react';
import { Nav } from './nav'

function Home() {
return (
<div>
<Nav />
<h2>Home</h2>
<p>Welcome to the documentation site!</p>
</div>
);
}

export default Home;

And now, it should look like this:

import React from 'react';
import { MenuItems } from './MenuItems';
import { Topbar, TopbarProps } from '@deletemangroup/microfe.navigation.topbar';

function Home() {
return (
<div>
<Topbar routes={MenuItems as TopbarProps} />
<h2>Home</h2>
<p>Welcome to the documentation site!</p>
</div>
);
}
export default Home;

Notice how I’m importing the new Topbar component as a regular module, instead of referencing the relative path of my file. This is thanks to the fact that I've created the component with Bit first. Bit created the symlink inside my node_modules folder so I could do this without having to first upload and publish the component somewhere.

I’ve also defined the list of routes (as you can see), inside another local file. It simply exports an array of objects like this:

export const MenuItems = [
{url: "/", text: "Home"},
{url: "/getting-started", text: "Getting Started"},
{url: "/api-reference", text: "API Reference"}
]

We can now proceed to do this on the other project, the my-blog project. We'll edit the posts for this one, when before they looked like this:

import { Nav } from "../nav"

export default function () {

return (
<div>
<Nav />
<h1>Post #1</h1>
<p>
This is the text of the first blog post
</p>
</div>
)
}

Now, they’ll look like this:

import { MenuItems } from "../BlogRoutes"
import { Topbar, TopbarProps } from "@deletemangroup/microfe.navigation.topbar"

export default function () {

return (
<div>
<Topbar routes={MenuItems as TopbarProps} />
<h1>Post #1</h1>
<p>
This is the text of the first blog post
</p>
</div>
)
}

As before, I’ve defined the list of routes for my blog inside a new file called BlogRoutes and now I’m importing the Topbar component instead of the local Nav one.

Wanna see the results? Simply go back to the browser, using the dev server, and visit each micro frontend, the automatic documentation will render each component for you:

We’re now clearly importing the same component, with the added benefit that now both micro frontends have the same styles on the nav menu (which wasn’t the case before).

The last thing to do, is to publish the new component, and to update the micro frontends by publishing the new, updated code into Bit.cloud.

We can easily do that with the following commands:

bit install add-missing-deps #to make sure all dependencies are already installed
bit tag #to version all components
bit export #to publish the new versions

Once you’ve tagged all components correctly, you should get something like this:

You can see how the new versions have been auto-assigned to both the micro frontends and the new component.

Now simply export them with bit export and you're done!

Compiling everything together

One last detail that I haven’t mentioned yet: thanks to Bit’s understanding of the relationship between these components (our new component and the two micro frontends), whenever we change our common code, it will automatically re-build all dependent packages (in our case, both micro frontends).

This is called RippleCI, and you can see it if you go to your scope and click on the RippleCI option. This is what I see when I push a new version of the Topbar component:

bit.cloud

You’ll notice how the Topbar component is being built, but the other two are queued up waiting for it to finish, so they can also be built (and tested).

If everything goes according to plan, you should see something like this (all green):

bit.cloud

This way, whenever you make a change to one of your components, all other components that use it, will be re-tested to make sure the updates are valid.

Cool right?!

Conclusion

Alright, we’re done.

During this article, we saw how you can use Bit to extract common code from micro frontends, turn that code into a component, and then reuse it everywhere. All with a single tool: Bit.

Why did we do this?

Because by doing so, we’ve:

  • Simplified to code of our micro frontends by removing code that didn’t have to live inside them.
  • Removed duplicated logic.
  • Homogenized the look and feel of our components by using the same one everywhere.

We did all of this without creating new repositories or assigning ownership of this new component to any external team. As you can see, through Bit, you can easily collaborate with others without too much hustle.

Let me know what you thought about it and if you have any questions, you can always check out the code of the components here.

To learn more about Bit, see the official documentation:

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/