Micro-Frontend Composition Patterns

Design patterns to help you build micro frontends effectively

Harry Martland
Feb 4 · 4 min read
Photo by Andrew Buchanan on Unsplash

When building micro frontends (MFEs), there are a, number of different techniques/patterns you can use. Here’s a list of them, including the pros and cons of using each.

MFEs split up a website into small separate services for pages and also components. An overview of MFEs can be found at micro-frontends.org.


Embedding

If a component changes a lot and is used by multiple pages, you may want to use this technique. Page services make HTTP requests out to component services, which return HTML. The component HTML response can then be concatenated into the rest of the page. This enables component teams and page teams to deploy changes at their own rate.

Resiliency considerations need to be made for this architecture as the component request could fail or become slow. Is the component mandatory? Can the page show a placeholder if the component fails? Component teams wanting to own the resilience of their service could provide a library that returns the fallback in failure scenarios.

Pages with multiple independent components are able to make component requests in parallel. Response times for components are still important as the page is only as fast as the slowest response.

Pages with a high load may want to consider caching components, especially if the components have low-input cardinality. To allow component teams control over the cache, TTLs pages can use the header to know how long the response can be cached for.

Components that have been broken out into their own service now have the option to be deferred to the browser. This is particularly useful for components that are slow to load — as the customer can start navigating your page whilst the browser loads components in the background. This does rely on JavaScript being enabled and modern browsers, but I think it’s fair to say we can assume this.


Templating

Pages and components don’t need to return a full response — they could return a response which contains placeholders similar to templates. The placeholders could be for content that needs to be localised or other components that need to be embedded. This allows for the MFEs to be simple, potentially just a static response built up in the routing service. This allows for cross-cutting concerns to be in one place.

One downside to this architecture is it makes testing individual applications more challenging (as you need to test through the service that fills out the template response). Parsing the templates may also be computationally expensive, especially when dealing with large responses. It may be more scalable and easier to test having the MFEs handle localisation and composition, keeping any proxy/routing services simple.


Libraries

Using libraries to distribute your components may be the most obvious and simplest composition architecture, but it’s worth calling it out.

As with any piece of software, if a module is used by many applications, it’s extracted out into a separate library. This can be done with front-end components, too. React and other front-end frameworks are great tools for extracting common components into libraries. If you’re not using a framework that supports this or you want to be framework agnostic, your component library can just return a string containing HTML.

Keeping applications running on the latest version can become a challenge when using libraries — as it is up to the application owner to update it as a dependency. There could be situations where different applications/pages are using different versions of a component giving the customer a confusing experience. For this reason, it’s best to use libraries when you know the component isn’t going to change often or you can update the consumers easily.


Headers

Headers are best used for data rather than UI components. If there’s a common set of data all front ends want access to, headers are a great way to propagate it. Generally, these headers are set by a gateway, but they could be set by a common library in all MFEs.

Keeping a common naming convention — e.g., a company or application prefix — allows applications to automatically proxy these headers to downstream services passing on the common data. A common example of this is customer tracking data that’s needed throughout the application stack.

The data in the header is best stored in URL-encoded JSON. This allows for the data to be sent safely but also allows humans to read it without decoding. Using JSON allows consumers to parse the data and also allow for the schema to change whilst maintaining backwards compatibility.

If the data required is from a complex source, it may be advisable to extract it from the gateway out into a separate service. This adds extra complexity, especially around resilience. What happens when the service is down? Some data/headers may be considered mandatory, in which case you may want to terminate the request with a non2xx status code. Others may be optional; however, it’s still advisable to set the header with a default value as to protect applications from null errors.


Conclusion

You should now have an understanding of some of the different ways to build MFEs and when it’s best to use them. An example application demonstrating some of these patterns can be found here.

Better Programming

Advice for programmers.

Harry Martland

Written by

Senior Software Engineer and Observability Guild Lead at Booking.com — Transport, writing mainly about observability and micro front ends.

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade