Building Microfrontends Part II — Joining apps together

So, on the Part I of this series we created a header (source), a products-list (source) and a cart (source). Now we want to put them together to create a complete page.

To add to our vocabulary: a page is a group of apps working together to show, well, a page for the user. For example, the homepage, the user’s account page, etc, those pages are composed of multiple apps working together.

How are we going to implement that? Well, there are a few possibilities.

Alternative 1

For the first alternative we will use the back-end to fetch the other apps. First, let's create a new project called homepage:

mkdir homepage
cd homepage
git init .
npm init

And install those dependencies that we are going to need:

npm install --save express ejs request

Now let's create an HTML with how we want our final homepage to be. For that, create a file inside views/index.ejs, it has to be on views folder because this is the default for express. Then add this content:

Here we are rendering a basic HTML, and our apps will be rendered where those ejs tags are. So now, we need our express server to provide those variables. Let's create our server then.

Create a file called server.js on the root of your project and put this code below:

Hopefully this is easy enough to follow, but to summarize, it requests all the other 3 apps in parallel and when all of them finish loading, it renders to the HTML.

You can see the final result with node server.js, check it out!

Cool! This setup should work in most cases. You can see the full code here:

Our architecture is like this now:

Each one of those boxes is a separate app, deployed in a separate server, running inside Docker, cool!

ps.: if you want a more robust way to join the apps on the backend, nginx SSI seems to be the right tool for the job. Here is a presentation about it.

This approach of joining is great if all your apps load fast, but that might not be your case, which leads us to our next topic…

Problem: Some apps may take longer to load

There are some cases where things take a while to load on the back-end, maybe your header loads much faster than the other parts, and you want to display that ASAP to your users, while the products list takes more time.

So, instead of waiting for everything to load and then display for the user, you want to display them as they load, like Facebook BigPipe (from 2009!).

First, we will simulate this problem. On our products-list app, lets wrap res.send on line 17 with a setTimeout of five seconds:

There, now it takes extra 5 seconds for this app to load every time, you can verify that by visiting and refreshing the page.

And now you can verify that our homepage app, the thing that joins the other apps also takes 5 extra seconds to load, leaving the user waiting in a white screen, and this is bad, how can we solve this?

Update 02/jun/2017

Although I present alternatives below, after hearing some feedbacks and stories, I've changed my mind. Currently, I would try to keep the first approach (joining everything in the backend) as much as possible, as it is a much simpler solution.

But Rogério, how do we solve the problem where some apps take longer to load then? The answer is: they shouldn't. Performance is a responsibility of the teams developing their apps, not the page joining tool.

I've seen this problem being solved by adding a politic where all teams had to render their apps in less than 300ms, and if it took longer, the request simply timed out.

But what about things that may be inherently slow? Well, the app does not have to fully render in less than 300ms, it has to render at least something, at least a simple "Loading…" text so the user isn't left waiting on a blank screen. It is a responsibility of the team developing the app to load things asynchronously if needed.

But what if you have a server-side rendering that takes long to load? Well, why do you have it then? Server-side rendering is used to speed up your app, not to slow it down.

I believe the main advantages of having a performance politic like this — instead of trying to solve the problem with code — are:

  • Much simpler approach
  • Distributes the performance responsibilities to the teams
  • Much less coupling, as you don't have specific APIs to adapt to, you just have to render your HTML
  • It is faster, as we don't need to load anything extra in the frontend to do the job

Ok, now back to the old me…

Alternative 2: iFrames

I know what you are thinking, "What? iFrames? Really?", then I ask you: why not?

Let's change our index.ejs to use them:

And since now we are loading the apps using iFrames, our server-side code can be much simpler:

There! iFrames solved our problem, you can see on the gif below things loading in a different order, and showing up as they load, so only the products list take the whole 5 seconds to show.

Okay, to be honest, usually iFrames are a not good idea, you will probably encounter problems as you advance.

The iFrames provide full encapsulation, and this is both good and bad. The good part is that it won't break stuff around it and vice-versa. The bad part is that you cannot share the stylesheet, loaded libraries, communication will be harder, etc, so I recommend the other alternatives on this page.

But, it is the easiest thing in the world to use. If you just want total encapsulation (like to embed something), an iFrame can be the solution, don't be afraid.

Here is the source code:

Alternative 3: Client-Side JavaScript

Instead of iFrames, we can load our HTML using JavaScript, which makes things much easier to manage. A first draft is replacing the HTML with this:

This basically loads the apps through ajax and insert their content inside those divs. It also has to clone each script tag manually for them to work.

But this doesn't work yet, we can look into the console to discover why:

Yeah, of CORS (ba-dum-tss), we cannot make ajax requests to other domains, but we can proxy them! Let's use http-proxy-middleware for that:

npm install --save http-proxy-middleware

And change our server.js to use it for proxying what we need:

Then we can now load those apps from /header, /productsList and /cart.

Woohoo working again! Now it also loads as soon as the apps are ready, check it out:

Source code:

ps.: to avoid problems with Javascript and CSS loading order, I suggest you to evolve this to a solution similar to facebook's bigpipe, returning a JSON like
{ html: ..., css: [...], js: [...] } so you can have full control of it.

Alternative 4: Progressive loading from the back-end

I believe the alternative above is very simple and works great! But, if it is very very important to you to have the HTML coming rendered from the server, maybe for SEO (maybe not, cause Google at least is already very good in processing javascript), you can use this alternative.

The idea is sending chunks of data from the server as they load, we can do that by changing our index.ejs file back to this:

And for the server.js to do what we expect, we can use this:

Wow! That is a lot of code just for doing this, if you know a better and simpler way to achieve that, do tell!

Basically, this uses res.write to flush all the pieces as they arrive, and finishes writing when everything is ready. The bad part is that they have to load in order, different from the previous example were the cart would load before the products list.

Here is how the final result looks:

Source code:

Alternative 5: WebComponents

If you heard about WebComponents before you might be thinking that they are perfectly fit for this.

But we will not cover them yet until the next part of this series, we have another problems to solve first.


Those alternatives above can make things very complicated IMHO, I'd stick with the Alternative 1 (simple fetch from the server) if you don't have that problem of some apps taking longer to load than others. If you do, first try to minimize the time on the backend of those apps rather than trying to fix it on the front, it will make everything easier.

You Don't Need a Separate Page

If you look at the diagram up top, you will see that we created a separate page for joining the apps together (to make separation of concerns more clear), but more than often, you won't want to create this extra complexity.

Instead, you could have products-list to be the page, which would include the other two, while at the same time, well, renders the products.

To summarize: don't create complexity until you need it.

Next Steps

Okay! So now that we have our apps split and know how to join them in a page, we can build other pages, reusing the same header for example, cool right? Not so fast, first we need to fix the problems that we caused for ourselves by using this approach.

Which problems you say? Go to Part III and find out.