Building Microfrontends Part III — Public Path Problem and Routing

Rogério Chaves
5 min readApr 4, 2017

If you follow the previous part and opened your console at some point, you probably saw those errors:

They are caused because the tags that load the styles and javascript for the apps (header, products list and cart) all use a relative url, e.g. /static/js, so when we include them in another domain, it tries to search the resources in a route that doesn't exist.

Fortunately, fixing that is quite simple for create-react-app, we just need to set an env variable called PUBLIC_URL and create-react-app will use that. So let's set this env var on the Dockerfile of our header app:

Then push the new version to heroku with heroku container:push web.

If you don't use create-react-app you can set the publicPath directly on your webpack.config.js as shown in the docs.

Do the same with products-list and cart, only changing the path and bam, the errors are gone! We can confirm that by looking at the Network tab:

But wait, something crazy happened, after loading the javascript, our page looks like this:

The products-list, that loads after the others, replaced the header. Why is that? If you look close, you will notice that all the apps have a
<div id="root"></div> on its HTMLs, and they all use React to render to this div, since javascript wasn't working before we were all fine.

It is easy to fix, but this is a problem that you will always have to keep in mind when using Microfrontends: be aware of clashing in shared spaces, such as the DOM, the window object and the CSS (WebComponents could help here).

Let's fix this, first, change the div on the HTML for something like this:

<div id="header-root"></div>

You also have to change your src/index.js:

ReactDOM.render(
<App />,
document.getElementById('header-root')
);

And server.js :

const rootElem = '<div id="header-root">';

Do the same with products-list and cart, and our problem is solved.

Using a centralized service for managing the URLs

We did solve the publicPath issue, but we created coupling between the homepage and the apps, what if one team takes care of developing the homepage, and another one the header? What if we want to add the same header to another page? To thousands of pages? And then we want to change it? This would be a big problem.

The URLs are what the pages use to include the apps. So, one idea to solve this problem is: having a centralized URLs service. It should provide an API for the apps to register their own URLs, and this service would be in the front of your website, just pointing to the other apps. We will call it Router.

This is how our architecture will look like with this:

Everybody talks to our router, which points things in the right direction, even John, our user.

We are basically applying the Dependency Inversion Principle here, now our homepage doesn't have a direct dependency with the apps, instead, they both depend on the same interface, the Router.

So, the best way to implement this router would probably be with an nginx proxy, or with your CDN service, like Akamai. But for the sake of simplicity, I've built a very basic one with NodeJS.

You can clone it from github and host on heroku:

git clone https://github.com/microfrontends/router
cd router
heroku create microfrontends -- choose another name
heroku addons:create mongolab:sandbox
git push heroku master
heroku container:push web
heroku config:set API_KEY=your_randomly_generated_key

Now we can use the Router API to add proxies:

# POST /api/routes { path : String, target : String }curl -X POST --data "path=/header&target=https://microfrontends-header.herokuapp.com" http://microfrontends.herokuapp.com/api/routes?api_key=YOUR_API_KEY

It will create a proxy for /header to microfrontends-header.herokuapp.com, we can do the same with products-list, cart and finally, the homepage, which will be at our root:

# POST /api/routes { path : String, target : String }curl -X POST --data "path=/products-list&target=https://microfrontends-products-list.herokuapp.com" http://microfrontends.herokuapp.com/api/routes?api_key=YOUR_API_KEYcurl -X POST --data "path=/cart&target=https://microfrontends-cart.herokuapp.com" http://microfrontends.herokuapp.com/api/routes?api_key=YOUR_API_KEYcurl -X POST --data "path=/$&target=https://microfrontends-homepage.herokuapp.com" http://microfrontends.herokuapp.com/api/routes?api_key=YOUR_API_KEY

And we can change our apps to point their public paths to the router now, on their Dockerfile change the env var:

ENV PUBLIC_URL https://microfrontends.herokuapp.com/header

This, together with the app having access to the Router API gives the devs the control that they need, where they can self-register their own URLs, and give it to the other teams to communicate.

Also, now the users may simply go to microfrontends.herokuapp.com, and from their point of view, it is one thing, a single website.

Here are the source codes:
https://github.com/microfrontends/homepage/tree/part-iii
https://github.com/microfrontends/header/tree/part-iii
https://github.com/microfrontends/products-list/tree/part-iii
https://github.com/microfrontends/cart/tree/part-iii

Alternative 5: WebComponents

Okay, now that we solved the CORS problem we can leverage on html imports to import our apps and render them on the screen.

First, we have to convert our apps into web components, for example, on the header, change the src/index.js to this:

Basically this creates a new HTMLElement called <microfrontends-header> and renders the react element to it once it is attached to the DOM.

For that to work, on public/index.html you will have to replace:

<div id="header-root"></div>

With:

<microfrontends-header></microfrontends-header>

That's it! Easy peasy! Do the same with products-list and cart.

Now, on our homepage, replace the HTML with:

That's it! So clean, so readable!

I've not tested very much, but it seems to me that WebComponents are indeed a good solution for loading apps in a page. Some problems I've found though:

  • WebComponents are still not fully supported in all browsers, with Mozilla holding back HTML imports, so you will need polyfills, more code for the user to load.
  • It haven't really gained popularity yet, maybe never will, I see blogposts from 2013 and still few people have tried it!
  • JavaScript bundle has to load first and register the components in order for the DOM to load, which means that to gain the advantages of server-side rendering you'll probably need to be more clever.
  • For this alternative we had to make changes not only on the homepage, but on the apps too, to convert them to WebComponents.

Source:
https://github.com/microfrontends/homepage/tree/webcomponents
https://github.com/microfrontends/header/tree/webcomponents
https://github.com/microfrontends/products-list/tree/webcomponents
https://github.com/microfrontends/cart/tree/webcomponents

Next Steps

Okay, so now we are loading our JavaScript… Oh no! We are loading JavaScript! Of all three apps, and they are all using React, so we are loading React 3 times, what a waste.

Let's see how to fix this on Part IV.

--

--