Building Micro Frontends with Components
In this article, we’ll share how our team is leveraging component-driven design and tools to build micro frontends, to enjoy a more efficient, fast, and scalable modern web development process.
Almost every team in the world, from global giants to small startups, relies on web application development to build, ship, and market their products to many people. This makes good web development an important goal for many teams.
It is only natural that more and more focus is being put into the web architecture and organizational structures that are necessary for complex, modern web development. One particularly interesting focus is given to the idea of “micro frontends”, which has recently gained favor with the rise of component driven technologies and tools.
So what are micro frontends and why do we need them?
In recent years, microservices allowed backend architectures to scale through loosely coupled codebases, each responsible for its own business logic. Each microservice exposes an API, is independently deployable, and is owned and maintained by a different team.
This paradigm provides great advantages to help accelerate, scale, and make the development process more efficient.
The idea of micro frontends is to bring the same advantages into the modern web development workflow. It means breaking down large monolithic projects into smaller, more manageable pieces, which are independently developed and owned by different teams, with the power to independently build and ship products simultaneously.
Source: This wonderful article by Cam Jackson
This architecture can provide teams with advantages such as simple, decoupled codebases, autonomous teams, independent releases, and incremental upgrades. The development process is greatly accelerated, scaled, and made more efficient in the same way that microservices changed back-end development.
Yet until recently, and despite being built with components, most web applications were still being built as single monolithic projects. GatsbyJS’s founder Kyle Mathews put it well saying that “Websites today are still made the same way they were 20 years ago, with a cumbersome monolithic approach to building sites, storing data, and delivering content. It’s time for a new way to build the web.”
So what exactly do you mean by “micro frontends”?
Micro frontends are usually thought of as a composition of independent frontends that happens at runtime, either on the server or on the client-side.
These sorts of implementations do have their own benefits (smaller payloads for client-side compositions, for example.) but they’re not, by any means, the only way to achieve “a composition of independently deliverable frontend applications” (to quote Cam Jackson).
This new way of building and collaborating on frontend apps, is, in our view, the core element of micro frontends. It is not limited to compositions in runtime. It is a goal that can be achieved by many models.
At Bit we rely on a good component model and a shared infrastructure for components that provide us with the benefits of micro frontends, so that teams can independently build components, and efficiently integrate to compose cohesive applications together.
For us, composing independent frontend apps in build-time brings the best of both worlds - the safety and robustness of “traditional monoliths” (with better integration tests, etc.) and the simplicity and scalability of micro frontends. For that, we used Bit, our own open-source library that helps in blurring the line between apps and components, by making each component completely independent.
At Bit we’ve been working with micro frontends since day one. We’ve been dogfooding our own tools to build micro frontends and adopt this approach for our frontend architecture as well as our organizational structure. Today, our tools help over 100,000 developers and organizations enjoy similar benefits.
In this article, I’ll share how our own team is building micro frontends with components. I’ll show and explain how we are able to split web development into decoupled and maintainable codebases, make it easy to replace or integrate new features and technologies, free teams to independently build and release changes to production without stumbling on each other’s toes, achieve up to 100% component reuse, and build both a scalable architecture and organization structure that smoothly grows as we do.
First, an example
If you head over to the homepage of Bit.dev, you’ll notice something strange happening every time you hover over an element on the page, such as a button or text paragraph. Each of these is a component.
Once the mouse enters the component, a highlight turns on indicating the component’s name, independent version, and scope in which it is published and exposed. As you scroll, you’ll notice that the entire page is made of components which are independently built, versioned, and shared by different teams, in different codebases, using different build processes, and are all integrated together into one cohesive-feeling product.
What you see there is a very real demonstration of how our team is using modern component-driven technologies like React and Bit to build micro frontends.
On the page linked above, you will see two sets of components, developed by two different teams. One is the “base-ui” set of components, owned by our front-end infrastructure team. The second set is “evangelist’”, owned by our marketing team.
These components don’t just compose our homepage; they can now be used to very quickly compose any new page or even application. For example, take a look at Bit’s Enterprise Page or Support Page to see more composition examples.
If you click on the scopes of the components you’ll be able to see our codebase architecture and our organizational structure with your own eyes.
One scope of components is called “base-ui”. This is the most basic component design system of Bit.dev, that contains basic elements like “paragraph” for example. It is owned by the frontend infrastructure team and developed in its own decoupled codebase. All these components are published and shared on Bit.dev. There, they can be easily discovered and integrated into new projects by any other team that needs them. And, the team building base-ui can keep incrementally sending updates to specific components.
The second scope is called “evangelist”. This is our concrete marketing-oriented system of components, used to build the marketing pages on our applications. It is autonomously owned by the marketing team and developed in a decoupled codebase. All of these components are published and shared on Bit.dev, and are maintained by the marketing team.
In this example, the marketing team was decoupled from the team building the Bit.dev web platform. This team works in a different codebase, releases changes through its own decoupled build pipeline, and can constantly deliver incremental upgrades.
Each team builds their components, with flexibility to split vertical ownership by features etc, in its own smaller and decoupled codebase. They use Bit to independently version, build, test, package, and publish each of their components. They use the bit.dev platform to host and expose components to other teams, so they can integrate and collaborate.
Every team at Bit enjoys a similar workflow. All teams work together to share and integrate components with each other, without stepping on each other’s toes. Close to 100% of the components written in our codebase are shared and reused, including not only front-end components, but also many other aspects of our system, such as “Search” features, “Playground” features, and even certain fullstack features that include both frontend and backend functionalities. We find this to be of great value.
KPIs and benchmarking we did for ourselves and for other teams show a variety of positive things happening when adopting this component-driven design. For example, the number of releases can go up by as much as 30X(!), the time spent on integrations is cut by over 50%, composition of new features becomes a matter of hours or days, and even on-boarding new developers can become a simple matter of hours instead of weeks. You can hear more about this change and what it can do for a fast-growing start-up first-handed at this great episode of the HeavyBit JAMStack podcast.
Our goals going into the process
Going into this journey, we had a few very well defined goals as to how we wish to benefit from adopting component-driven micro frontends, and what we will consider as a “success” for our internal process. Here are the key goals we set for ourselves.
Decouple codebases, making each feature much easier to develop, test, maintain, and upgrade. We especially wanted to make it very easy to fix, replace, or add any feature or technology in our applications, so that we can constantly keep releasing new features to our customers.
Independent teams that can autonomously build and release features to production, without having to wait or step on the toes of other teams, and without breaking stuff they shouldn’t break in a way that blocks the work of other teams. This means a decoupled release pipeline for each team, so that there’s no more fighting over master branch or waiting for versions releases while they bloat.
Rapid, incremental upgrades so that each team can constantly deliver new features, upgrades, fixes and rollbacks to their products used throughout our applications, without breaking functionality or user-experience at all.
Quick, safe integrations so that every team will expose its features and functionalities and make them available to other teams to easily discover and integrate into their existing applications without refactoring entire applications.
Achieve 100% component reuse to speed development times, ensure design consistency, and improve the quality of our software applications. We wanted to make it easy for all developers to quickly share components with each other, while being able to efficiently regulate and standardize the process.
Standardize development so that despite their independence, all teams will develop, build, test, and release components in a standardized way using the same development environments and through standard build pipelines.
Our shared component infrastructure
Naturally, different teams use different stacks and tools to build their technologies.
For the development of our web platform and websites we chose React. With the release of features like Hooks and Context-API, React became a great choice for us to develop modern applications from smaller, independent, and reusable pieces.
For a shard infrastructure that supports component-driven micro frontends we used Bit’s open-source tools and cloud platform. Apart from dogfooding our product on a daily basis, Bit provides us with a few necessary features for adopting our micro frontends architecture:
Our OSS tools let us develop many modular components in any given codebase, with a modular workspace that helps us build with independent components. Components can be independently developed, rendered, built, tested, versioned, published, and even go through CI without being coupled to any specific project.
In addition, Bit provides two more critical features: First, it provides universal control over the dependency graph of all components. It automatically resolves each component’s dependencies, and lets you know exactly what should change whenever there’s a change to any dependency. Second, it provides 0-config reusable and customizable development environments so that components can go through their own build pipeline in separation from any larger project, and these changes can be built across the entire dependency graph of components.
This sounds like a lot, and it is, but Bit actually makes it quite simple to build independent components that can be developed and released on their own.
Our cloud platform provides our team (and other teams) with a collaboration hub where everyone can easily publish their components, visually document them (Bit automates this process), discover and install them. This lets our teams share and reuse close to 100% of the components we build.
To make sure each frontend gets its own independent and fast build process, Bit also provides a unique CI/CD process that is 100% component-driven, which means that different teams can safely integrate changes without having to wait, fight over master, or break anything. Developers can continuously and safely propagate changes to components across all impacted applications.
Instead of building every monolithic project in one build process that all teams have to go through, the component-driven CI splits the build process so that it only runs on the components that actually changed, and propagates the changes infinity up their dependency graph, to build every impacted component, on every page, in every app.
This lets us free the release pipeline of different teams from each other, so that each team can independently and continuously release changes and updates to production. It also means about 50X faster builds, since not everything is built. And, we can pinpoint issues and bugs down to specific components.
Successful changes can be automatically translated into pull-requests that are automatically sent to all relevant projects, so that their maintainers can safely adopt the changes and make sure their applications are always up to date with the latest versions. Bit.dev also provides us with an overview of all components in our different projects, so that we can monitor and regulate exactly who’s using which component in what version.
Conways’ Law states that there’s a strong correlation between software architecture and the organizational structure. That is very true when building micro frontends, as the main goal is to make the organization more efficient. Decoupling codebases, enabling integrations, or splitting the release pipeline, are all ways to build better software as well as autonomous agile teams.
A small team empowered to make decisions and relentlessly drive toward their goals will deliver results and insights much more quickly than a larger group. After all, who knows the product’s users and problems better than the team who owns it, right?
Thanks to the flexibility of our component-driven architecture, we were able to assign team ownership in a much more dynamic, vertical and context-relevant way. Instead of having a “front-end team” for bit.dev and a “marketing website team” work together on a monolithic app, we completely separated these teams in every way.
To start with, a few developers are considered the “front-end infrastructure team” and are reporting directly to maintain the bit.dev platform’s basic set of components. On this team there’s also a designer, a product manager and a few more stakeholders. They build, release, and update their features independently from other teams that use them.
The “Component Search” team, responsible for the complex search feature on bit.dev, is made of a few developers (both front and back), one NLP researcher, and a product manager. It exposes components for other teams to integrate into their products.
The “Component Discovery” team includes a few developers, a part-time designer, and a part-time product manager, to build the set of components used to document, visualize, and interact with the components shared on the bit.dev platform. In fact, thanks to Bit, the same documentation components that are used both in Bit’s local development workspace, as well as in the cloud platform, are all built by this team.
A few more developers were assigned to the marketing team to build the marketing set of components, along with a few marketers and a designer, and these components are used to compose our marketing website as well as a few more applications.
The customer success team, with its own developers, builds and maintains its own set of components that implement different user interactions used across the Bit.dev platform, and even in additional websites and applications we build.
And so the list goes on, while each feature becomes a team autonomously responsible for building and shipping components, for other teams to integrate and use as well.
Simple, decoupled codebases
Decoupling codebases makes features easier to develop, test, maintain, replace, or upgrade. We wanted to be continuously able to add new technologies and features.
Each of our teams works in a completely different and decoupled codebase. It develops, tests, and maintains its features without the limitations and pains of working inside a complex monolith with other teams.
The source-code for each feature or product is naturally much smaller and simpler than that of the entire application, which makes each codebase much easier to develop, test, and maintain without the limitations and pains of working together inside a monolith.
If we look back at the “base-ui” and “evangelist” examples on the bit.dev homepage, you’ll notice that each set of components is developed in a different codebase. Both sets are decoupled from each other, and are integrated together to create different pages, features, and applications.
Since all codebases are decoupled from each other, and each is composed mostly if not entirely out of components, it also becomes much (much) easier to gradually refactor parts of our applications, so that we can quickly and safely add or replace technologies.
Over time, having to clearly define what code belongs in each codebase, is a great way to better understand the architecture we’re building, and the role of each part inside it.
We are able to split and decouple the release pipelines of different teams from each other, so that every team can independently build and release changes to multiple applications, without waiting for version releases while they bloat or fight over master branch.
As mentioned above, through Bit and Bit.dev we are able to assign a custom yet standardized build pipeline for every team’s set of components. So despite being built separately, all components can go through the same tasks and tools.
Then, the (50X faster) component-driven CI process on Bit.dev (in Beta) maps and builds changes up the propagating dependency graph of all dependent components across every single page and every single application. Put into simple words, every team can make a change to any component, independently build these changes, and learn if and how it breaks other components and other teams across all relevant applications.
If all is well, the changes are automatically translated into a pull request that is sent to all relevant projects, so that their owners can update their components. They even get notifications and reminders via Slack. In the Bit.dev “Projects” feature (another one of our teams), we can view and monitor who adopted which PR and where.
So instead of messing with cumbersome iframes or finding alternative solutions, we rely on build-time integrations that do not couple our release processes together. For now, this works amazingly well for us, and our component-driven CI will be out of Beta within weeks, so feel free to take a test drive when it’s out too. In the future, we do have a plan to go all the way and enable component-driven deployments to applications.
Now that our codebase is built from decoupled smaller codebases, which are built with modular components that are independently versioned, we have gained a very useful ability to easily add, replace, hotfix, or even rollback a single component or feature.
Teams can make case-by-case decisions, rapidly change and deliver upgrades and features, and treat refactoring as a gradual on-going process that can be done one piece at a time. Also, our entire software becomes more resilient, as it becomes almost impossible for developers to break things outside the scope of their components.
One thing that we especially enjoy, is the ability to respond fast to user requests. When a developer asks for a new component build environment that supports a certain Stencil version, for example, we can create and add it in a few hours instead of weeks. And, since much of Bit is open-source, we can make it easy for people to extend or replace any part of their toolset on their own. This makes our entire team much more efficient.
Infinite reuse and collaboration
We publish all our components to Bit.dev. There all our teams share, discover, collaborate, and reuse components with each other in fun and effective ways.
Added features such as visual component documentation, a smart component search, and even live simulations, help to make all our components discoverable so that we don’t have to maintain any additional documentation websites, registries or tools.
This collaborative system makes it easier for developers to share code, suggest feedback, and ensure consistency. We especially enjoy the fact that it helps new developers who join the team cut their learning curve, as they can find and start using existing components, so that they can hit the ground running and start building. It’s also a useful way for us to improve collaboration between developers and other stakeholders like our designers and product who can now see all our components.
Some say micro frontends are a “good component model”. For us, it was not just a good component model, but also a better way to build our own team, improve the way we work, build better modular software, and deliver it faster and more often.
For larger organizations, independent team delivery is a true game-changer in their ability to build and ship successful web applications. So is the ability to standardize component development, and allow teams to share and collaborate with each other.
For smaller teams, adopting a flexible and scalable architecture will improve their ability to grow, rapidly add new features, on-board new people, and focus on core technology and true innovation.
I hope you’ll find something in the idea of component-driven micro frontends that can be useful for you, and thanks for reading!