Setting up micro-frontends with Astro and Ecma Script Modules

Sergio A. Arevalo Soria
4 min readMar 16, 2023

--

Luca Mezzalira, perhaps the father of micro-frontends defines micro-frontends as “the technical representation of a business subdomain” that allow independent implementations with the same or different technology.
As such, they are an excellent case for large companies that want to divide their frontend applications into subdomains. Allowing these to be deployed independently.

Monolith vs micro-frontends

However there is always a risk of taking on incidental complexity when setting up a micro-frontend architecture. One way to reduce complexity in the composition of micro-frontends is to simply compose them as EcmaScript Modules (ESM) using URL imports.

Composition

EcmaScript Modules lets you import modules from an external url, just like any other module. So code wise in a React app you’d end up with a couple of lines of code in order to import a micro-frontend:

import MicroFrontend from "http://localhost:7100/bundle.js";

const Component = () => (
<React.Suspense fallback=”Loading…”>
<MicroFrontend />
</React.Suspense>
);

In this case, MicroFrontend is deployed independently and is exported as a standard component. It could be as simple as this.

const MicroFrontend = () => (
<section>
<h2>MicroFrontend</h2>
<p>...</p>
</section>
);

export default Microfrontend;

As long as it’s bundled to ESM with a build tool like Vite and it provides a default export, it can get imported as an external module. This makes micro-frontend composition super easy, using JavaScript’s native module system as the interface. No need for complex frameworks or plugins.

Astro

In a micro-frontend architecture you usually have a Shell app that does the composition. Astro is a meta-framework that provides good performance, is built on top of Vite and bundles to ESM. It also implements the Island Architecture, meaning that we can use different frameworks such as React, Vue or Solid on the same page. It therefore is an excellent choice to use as a Shell.

To import two micro-frontends in Astro, all you need is to add the appropriate framework integration and import them with URL imports.

import MicroFrontendA from “http://localhost:7100/bundle.js";
import MicroFrontendB from “http://localhost:7200/bundle.js";

const ReactComponent = () => (
<>
<MicroFrontendA />
<MicroFrontendB />
</>
);

export default ReactComponent;

With this, ReactComponent can be imported in index.astro as usual with <ReactComponent client:only=”react” />

Shared dependencies

To share dependencies across applications we can use Import maps in order to fetch and align them from a CDN, like esm.sh. These dependencies are only fetched once on the first page load and gets cached in the browser.

<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react",
"react-dom": "https://esm.sh/react-dom"
}
}
</script>

Note that each app has to set react and react-dom as externals at build time. This can easily be achieved in a Vite config.

build: {
rollupOptions: {
input: "src/MicroFrontend.jsx",
preserveEntrySignatures: "exports-only",
external: ["react", "react-dom"],
output: {
entryFileNames: "bundle.js",
format: "esm",
},
},
},

In order to get this working in the Astro Shell app, we have to add the externals in a client side integration:

{ 
name: ‘importmap-externals’,
hooks: {
‘astro:build:setup’: ({ vite, target }) => {
if(target === ‘client’) {
vite.build.rollupOptions[“external”] = [“react”, “react-dom”];
}
}
}
}

There are several strategies when it comes to sharing dependencies. If we wanted to be more lenient we could allow different React versions, by letting each micro-frontend export injection functions that can then be used to mount it on a div in the Shell app.

In this article I’ve used a solution that aligns React versions with a centralised import map in the Shell app. This ensures that we only download React once on the first page load.

Conclusion

Using Astro, Vite and ESM imports we can easily set up a micro-frontend architecture with close to zero complexity in its composition and management of shared dependencies. As a bonus, Astro also has a feature that lets us load in micro-frontends when the user sees it (i.e client:visible).

Interested in trying this out? I’ve set up a full example with two React micro-frontends and one written in Solid. You can see it on GitHub here: https://github.com/sasoria/astro-microfrontends

Thanks to Fred K. Schott for inspiring this solution by stating that micro-frontend composition was essentially a solved problem on a particular ESM build tool, since files “can be loaded individually, on demand, in production”.

--

--