Facile.it features several landing pages to promote its main products (insurance, mortgages, etc.). These pages are composed of several “blocks” containing static information from a CMS. They then link to interactive sections of the website where users can enter their information to receive a quote.
For UX and SEO reasons, Facile.it wanted to allow users to start interacting directly from the landing pages, integrating dynamic forms within the landing page blocks.
However, this posed a challenge: the landing pages and the dynamic pages are managed by different teams. How could one team “provide” a dynamic section so that it could be included in the landing page by another team?
The solution we needed had to allow all teams to evolve their own part of the product independently, while providing flexible integration points.
Micro frontends should solve this, right?
To recap, we have:
- One team that manages the landing pages, which are obtained by assembling static blocks from the CMS and, if we are successful, dynamic blocks from other teams.
- Several teams that manage what we call “dynamic” pages, i.e., interactive pages linked from the landing pages. These teams want to integrate some blocks of their pages into the landing pages (e.g., a form to collect some initial data from the user).

Micro frontends immediately seemed like an obvious solution to the problem: we have multiple “frontends” to integrate, managed by different teams; that’s why this architectural pattern was born.
However, micro frontends come in many shapes and forms, and not all of them are suited for the specific business needs. Specifically, in the context of Facile.it, it was absolutely key to:
- Server-render the entire page, including dynamic blocks.
- Avoid introducing strong coupling between the team managing the landing pages and those responsible for the dynamic pages.

For example, many popular micro frontend solutions, such as single-spa, require strong coordination between front-end applications, e.g., by enforcing the use of a specific build tool like Webpack. The Facile.it team initially explored this solution, but opted against it due to the technical constraints it would have imposed on the development teams.
A tailor-made architecture
After an initial analysis, Buildo envisioned a new architecture that builds on several proven technologies combined in a novel way—something that didn’t exist off the shelf and was perfectly tailored for Facile.it’s business needs.
We then built a working MVP of the solution: we did not stop at prototyping; instead, we worked directly with Facile.it’s technical team to deliver value immediately.
We documented the solution, created specific artifacts to accelerate adoption across other internal teams, and supported Facile.it’s tech leads in making the new solution production-ready.
Micro frontends without a framework
Constraints and challenges
Adding to what we discussed above, we benefited from specific technical constraints that limited the scope of our analysis:
- All teams at Facile.it use React.js.
- Almost all teams use Next.js.
- All teams share a Design System library (although they might be on different versions).
The main challenges we wanted to address were:
- Integrating a “dynamic block” (i.e., a standalone React application) within the landing page (which is a Next.js app).
- Making the “dynamic block” available from another Next.js app, sharing the existing infrastructure. In other words: the same React component had to be usable directly within the host Next.js app or served as a standalone block for inclusion into the landing page.
- Achieving all of the above while preserving Server Side Rendering (SSR).
Secondary challenges were:
- Avoiding duplication whenever possible: e.g., preventing the inclusion of the same version of React multiple times on the page (the same applied to other shared libraries like the design system).
- At the same time, we did not want to impose specific library versions on any team. In other words, we wanted to allow teams to use different versions of React, the Design System, etc., but enable deduplication if they happened to use the same version.
Serving the dynamic block
Due to its architecture, Next.js can only output entire applications that take over the entire DOM.
We then needed a way to bundle a single component and its related assets and serve them via Next.js.
The solution we eventually landed on uses Vite with a custom backend integration: this way we could “package” existing React components into standalone applications that could be server-side rendered and then served over the network.
Once we validated that the solution was working, we could package everything into a custom Vite plugin to hide the complexity and make it straightforward for each team to configure.
Similarly, we provided a utility library to serve the component using Next.js’s route handlers, making the integration as concise as possible, without leaking the internal complexity.
Integrating the dynamic block in the landing pages
In the Next.js app rendering the landing pages, we retrieved the URL of the dynamic block from the CMS and “materialized” it by fetching its text content.
This gives us the server-side rendered HTML of the block, which we then can include in our own landing page using the dangerouslySetInnerHTML functionality from React.
This way, the HTML content of the dynamic block is inserted right away during the first server-side render of the landing page, satisfying our main SEO requirement.

Avoiding duplication
Including multiple dynamic blocks without a bundling step presented another challenge: each block, as well as the host application, would include its own version of React and other common libraries, such as the design system component library.
Our two goals here were:
- Avoid including the same version of a dependency twice in the page.
- Avoid forcing a specific version of a dependency on all teams, since this would introduce the need for coordination across teams, which chips away at the benefits of working independently on separate parts of the frontend.
The solution we landed on leverages the import module, which is widely available across major browsers.
The general intuition is that instead of bundling the shared dependency, the host application and all the blocks would import it from a common URL, specifically an ESM-capable CDN, like https://esm.sh.
The interesting bit to notice here is that browsers automatically cache and de-duplicate imports from the same module, so we gain these properties:
- If two dynamic blocks import the same version of a library, this will result in only one network call.
- If two dynamic blocks import two different versions, this will result in two network calls.
This meets our goals: optimize whenever possible, while still allowing flexibility to the different teams.

Meeting business needs with tailor-made solutions
At Buildo, we always look beyond technical trends and focus on our clients’ business needs; this allows us to craft tailor-made solutions that truly meet their goals.
In this case, we chose to discard well-established solutions like Single SPA or Module Federation as they would have introduced too many compromises for Facile.it. Instead, we used our deep knowledge of frontend architectures to compose lower-level components into a well-crafted solution that’s tailored to their specific requirements.
The solution has since been fully integrated, released to production, and is performing as envisioned, which is ultimately the best metric for determining its success.