Example of Micro-Frontends using Single-Spa Framework and React

Admir Cosic
9 min readJan 16, 2021

--

If you don't know that much about microservices then please read the few headings below before reading the rest of the story. As the people that are intended as audience for this story are people that have pre-existing knowledge of the microservice pattern.

Why Microservices?

Microservices have been making waves among forward-thinking application development organizations since the term was first coined in 2011. In other words microservices have been around for a long time. Despite being a more complex solutions than the more traditional Monolith. Skip ahead to 2021 and nowadays microservices is the preferred pattern when building back-ends.

With microservices, your code is broken into independent services that run as separate processes. Output from one service is used as an input to another in an orchestration of independent, communicating services. Microservices is especially useful for businesses that do not have a pre-set idea of the array of devices its applications will support. By being device- and platform-agnostic, microservices enables businesses to develop applications that provide consistent user experiences across a range of platforms.

Resilience

With microservices, your entire application is decentralized and decoupled into services that act as separate entities. Unlike the monolithic architecture wherein a failure in the code affects more than one service or function, there is minimal impact of a failure using microservices. Even when several systems are brought down for maintenance, your users won’t notice it.

Scalability

Scalability is the key aspect of microservices. Because each service is a separate component, you can scale up a single function or service without having to scale the entire application. B

The right tool for the right task

With microservices, you don’t have to get tied up with a single vendor. Instead, you have the flexibility to use the right tool for the right task. Each service can use its own language, framework, or services while still being able to communicate easily with the other services in your application.

Time to market

Because microservices works with loosely coupled services, you don’t need to rewrite your entire codebase to add or modify a feature. You make changes only to a specific service. By developing applications in smaller increments that are independently testable and deployable, you can get your application and services to market quicker.

Debugging and maintenance

With microservices smaller modules go through a continuous delivery and testing process, your ability to deliver error-free applications is vastly improved.

Improved ROI

Microservices also allows you to optimize resources. With microservices, multiple teams work on independent services, enabling you to deploy more quickly — and pivot more easily when you need to. Development time is reduced, and your teams’ code will be more reusable. By decoupling services, you won’t have to operate on expensive machines. Basic x86 machines will do. The increased efficiency of microservices not only reduces infrastructure costs, it also minimizes downtime.

Continuous delivery

Unlike monolithic applications, in which dedicated teams work on discrete functions such as UI, database, server-side logic, and technological layers, microservices uses cross-functional teams to handle the entire life cycle of an application using a continuous delivery model. When developers, operations, and testing teams work simultaneously on a single service, testing and debugging becomes easy and instant. With this approach of incremental development, code is continuously developed, tested and deployed, and you can use code from existing libraries instead of reinventing the wheel.

Overview of the finished app

The finished app will be an app that consists of four different sub-apps. the sub-apps all can live in their own separate repositories. I have chosen not to setup a CI pipeline but you could set up continuous integration for each of the apps that are part of the finished app. Each CI pipeline would bundle the JavaScript for a micro-frontend app and then upload the resulting build artifacts. The four apps are.

  1. A root container app “root-config” that serves as the main page container and coordinates the mounting and unmounting of the micro-frontend apps.
  2. A micro-frontend “nav-app” that’s always present on the page and displays the navbar.
  3. A micro-frontend “app 1” that only shows when it is active.
  4. A micro-frontend “app 2” that also only shows when it is active.

Lets get started

To generate the apps for this example, we’re going to use a command-line interface (CLI) tool called create-single-spa. Please install it via npm before entering the commands below.

Open a Command prompt/Terminal in a fold of your choosing and follow the steps below to create the container app (which we call the root config):

mkdir micro-frontend
cd micro-frontend
mkdir root-config
cd root-config
create-single-spa

You will get some CLI prompts after the last command. Enter the answers below when prompted.

  • Directory for new project => go with the default so just press enter.
  • Select type to generate => “single-spa root config”
  • Which package manager do you want to use? => i picked “npm” but yarn works also.
  • Will this project use Typescript? => i picked “No” but if you are planing to use Typescript then pick “yes”.
  • Would you like to use Single-spa Layout Engine? => “No”
  • Organization name => this is also up to you i entered “admcos”

Now it is time to generate the micro-frontend apps. To do that follow the steps below for each of them. Of-course replacing “$app” with the micro frontend app name.

cd ..
mkdir $app
cd $app
create-single-spa

You will get some CLI prompts after the last command again for each of the apps. Enter the answers below when prompted.

  • Directory for new project => again use the default, so just press enter
  • Select type to generate => “single-spa application / parcel”
  • Which framework do you want to use? => i picked “react” but this is up to you
  • Which package manager do you want to use? => i picked “npm” but yarn works also.
  • Will this project use Typescript? => Again i picked “No” but if you are planing to use Typescript then pick “yes”.
  • Organization name (can use letters, numbers, dash or underscore) => enter the same organisation name as for the root config app in my case that is “admcos”
  • Project name (can use letters, numbers, dash or underscore) => here enter the app name.

The end result file structure should be something similar to the following image. Namely one container app, that is the root-config app and also three micro-frontend apps.

All four apps generated. Each is hosted in its own repository.

Now it’s time to hook our apps together and register them within the container app.

Registering Micro-Frontend Apps

One of the container app’s primary responsibilities is to coordinate when each app should be shown or hidden. To help the container app understand when each app should be shown, we provide it with “activity functions.” Each app has an activity function that simply returns a boolean, true or false, for whether or not the app should be shown or not.

inside the root-config/src/ folder create a new .js file called activity-functions.js and paste into it the code below.

the newly created activity-functions.js file

Next, we need to register our three micro-frontend apps with single-spa. To do that, we use the registerApplication function. This function accepts a minimum of three arguments: the app name, a method to load the app, and an activity function to determine when the app should be shown or rather is active.

Open the root-config.js file in the /root-config/src/ folder. In my case it is named admcos-root-config.js and enter the code below with minor modifications namely replacing the organization name.

The updated root-config .js file

Now that we’ve set up the activity functions and registered our apps, the last step before we can get this running locally is to update the index.ejs file in the same directory. We'll do two things here.

  1. Add the following code inside the head tag to specify where each app can be found when running locally.
the updated index.ejs file

2. Add some HTML to serve as the main content containers for the page.

html syntax that we add to index.ejs

And now finally we are ready to test run the application locally.

Local Test Run

To get our app running locally, we can follow these steps:

  1. Open four terminal/command prompts, one for each app
  2. For the root config, in the root-config directory: `npm start` (runs on port 9000 by default)
  3. For the micro-frontend nav app, in the nav-app directory: `npm start --port 9001`
  4. For micro-frontend app 1, in the app1 directory: `npm start --port 9002`
  5. For micro-frontend app 2, in the app2 directory: `npm start --port 9003`

Now, we’ll navigate in the browser to http://localhost:9000 to view our app. We should see…

Its alive :)

some text! Super exciting right, and if we go to http://localhost:9000/app1 We should see…

That both the nav-app and app1 micro-frontend apps are loaded without issues.

If you do not see any text then you need to add the following two lines to the systemjs-importmap in the imports json above the single-spa entry.

"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js","react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js",

And now its time to style the nav-app application and the two other micro-frontend applications. Ill leave the styling up to your own style. My quick styling resulted in the image below.

The fabulous styling is applied to the application

Adding React Router

The last small change we’ll make is to add React Router to our app. Right now the two links i have placed in the navbar are normal anchor tags, so navigating from page to page causes a page refresh. Our app will feel much smoother if the navigation is handled client-side with React Router.

To use React Router, we need to install it in the “nav-app”. From the terminal, in the nav-app directory, we'll install React Router using npm by entering npm install react-router-dom.

Then, in the nav-app directory in the root.component.js file, we need to replace the anchor tags with React Router's Link components. You can copy the code below with minor modifications.

The updated root component of the nav-app

Now, we’ll navigate in the browser to http://localhost:9000 to view our app. We should see…

And we see that the react-router changes are working as expected

And we are now done with the basic setup, the different apps are in their own repositories, we have tested that everything is working locally and also working as expected. Whats next you might ask? Well that's up to you

Next logical step would be getting it ready for production. For an explanation of what i mean by this look below.

Getting Ready for Production

At this point we have everything we need to continue working on the app while running it locally. But how do we get it hosted somewhere publicly available? There are several possible approaches we can take using our tools of choice, but the main tasks are 1) to have somewhere we can upload our build artifacts, like a CDN, and 2) to automate this process of uploading artifacts each time we merge new code into the master branch.

I will not go into how to get it ready for production because this is more of an small experiment but if you have come this far you will probably already have the knowledge required to setup a CI/CD pipeline but there is one thing which is very important you will need to create an “Import Map” for production which you have to host somewhere. That is needed after the CI/CD pipeline is implemented because there’s still one thing missing: How do the new build artifacts get referenced in our container app? In other words, even though you’re pushing up new JavaScript bundles for the micro-frontends with each new update, the new code isn’t actually used in the container app yet! I would recommend to you to read the documentation at https://nicedoc.io/WICG/import-maps for an explanation about how import-maps are defined.

I hope you enjoyed this and that it has awakened your interest into this interesting approach to front-end applications. The full code can be found on Github at https://github.com/adodado

Thanks for reading and feel free to comment or reach out if you have questions, feedback or just want to collaborate on a story on medium.

--

--

Admir Cosic

I am a developer who is passionate about stuff like IOT and home automation. Few tech that I enjoy working in are .net core, C#, Typescript and Azure.