At smartive we have built a large ecosystem of reusable, customizable Micro Frontend Components which are used across 30+ websites of the biggest employer in Switzerland. These are our learnings.
It is a collection of different small components such as login, user profile, newsletter, ratings & reviews and many more, which all share a common concept.
They can all be customized in their look and feel to match the CI/CD of the hosting page. Under the hood they share the same user login context and messaging API to talk to each other.
A new theme for all components can be added in the blink of an eye. We’re providing a utility library to abstract complicated concepts and provide simple, reusable hooks. This means, getting the user context is as easy as calling a useUser() hook.
Within one year we counted a total of 1 442 micro frontend component releases from 35 different authors on our setup.
Let’s dive in and take a look at our setup and the challenges we were facing. The micro frontend components are built as React components written in TypeScript, documented in Storybook and altogether stored in a Monorepo.
If you want to read more about the benefits of separating your components into micro components, check out our previous blog post:
A monorepo (mono repository) is an old concept where one source code repository stores all your code and assets of multiple projects — not to be confused with a monolithic application.
Benefits of a Monorepo
We still use self-contained and clearly separated micro components hosted in the same repository, but take advantage of the increased visibility of components within a monorepo:
- Reusability — Inside the monorepo existing components are easy to discover, integrate and test.
- Refactoring — It is much easier to introduce repository wide refactorings, new features (e.g. switch to a new single sign on) and breaking changes. Without a monorepo, adopting new major releases with breaking changes of individual component libraries takes months, whereas the monorepo allows us to anticipate a large part of the changes.
- Dependency Management — Much simpler dependency management through visibility. If you make a change you can see immediately what else will be impacted. Also you don’t have to manage dependencies between the components in the monorepo.
- Team Collaboration — Every developer on the monorepo can see what is going on, which facilitates team work and inter-team collaboration.
- Separation— Each component in the monorepo must be clearly separated from each other through explicit APIs. When code is being reused and cross-linked in an arbitrary manner, the project risks to become a giant messy code monolith.
- Ownership & Coordination — Each part of the monorepo needs a clear owner who is responsible for updates, inter-team coordination and support.
- Build & Test Time — Due to multiple components within one repository the integration build and test pipelines will take increasingly longer to complete.
To overcome those limitations of a monorepo we have 3 conditions in place:
- Independent Components — Never directly reference another submodule’s internal code. Only explicitly exposed components may be reused across modules & communication must always happen via a well defined messenger bus or the existing DOM API
- Consistent Reuse — Share the same config for build, test, linting and documentation, so that all modules share a commonly agreed basis and will stay consistent when the repository evolves.
- Documentation & Communication — Every component must be documented on a shared storybook instance which is publicly available. Each component must keep a changelog, which is distributed to all stakeholders for new releases.
Together with our commitment to peer reviews and testing, this keeps our quality high and the monorepo clean and stable. More about how we are constantly increasing our quality on the following blog post: Quality Assurance, Testing and Tools.
Agree on a Common Concept
Make sure everyone is on the same page. Define a monorepo wide eslint config, typescript config, linting rules and testing setup.
Agree on a common Definition of Done and browser support matrix. Provide and update documentation with every change. If needed, unify the use of 3rd party libraries and their version (e.g. react, react-dom versions).
Define how components interact with each other (e.g. via DOM API) and design them to use lazy loading whenever possible. This makes it much easier to reuse and integrate your components.
Since there are many different projects and developers working in parallel, try to automate as much as possible.
Use conventional commits to automate the changelog generation and package versioning using semantic versioning. Generate automated weekly release mails. Setup a continuous integration and continuous delivery pipeline with automated testing, releases (semver) and documentation deployments.
Keep an eye on developer performance. It can get quite frustrating if the pipeline of merge requests is taking longer and longer to complete or, even worse, fails randomly on unrelated tests. Make sure to have a solid, reproducible and fast pipeline.
We use jest and TestCafe for automated unit and integration tests across all components. All micro component module tests run in their own Gitlab CI job to help speed up the testing process and make the pipelines more resilient.
Communication and Stakeholder Management
Communication is key. Not only with your stakeholders but also with all project teams working on the same monorepo. Make sure everyone knows and uses clear and predefined communication channels for support, new releases (mailing) and changelogs.
Provide documentation (e.g. Storybook) that is always up to date and accessible to everyone. There should be no need to dig into the codebase to get a grasp of the components.
Although challenging, we are happy with the setup and progress so far. There are many obstacles on the way but the benefits of a monorepo outweigh its drawbacks and make managing and contributing to a huge ecosystem of micro components much easier.
Make sure to agree on a common concept and well defined design patterns and automate as much as possible, to keep focus on features instead of managing the system.
Wanna know more about the technical setup and how we integrated and automated the release process? Then you need to check out our follow-up post with some more in-depth technical details:
What are your experiences with Micro Frontends and Monorepos? We would love to hear about your challenges and how you solved them! Let’s get in touch!