Server-side rendering micro-frontends with Astro

Sergio A. Arevalo Soria
3 min readMay 20, 2023

Ruben Casas once said “something that is really hard with micro-frontends is server-side rendering” and Luca Mezzalira recently stated that “server-side rendering is the real challenge”. I tend to agree, but luckily there are always ways to mitigate complexity.

I have previously written an article on how easy it is to set up a micro-frontend architecture with EcmaScript Modules (ESM) using Astro and as it turns out, its almost just as easy to do server-side rendering (SSR).

Composition

In order to server-side render micro-frontends I’ve used server-side composition, that is, composing micro-frontends in the server. Meaning that “the page is already fully assembled when it reaches the customers browsers”.

Server-side composition

This allows us to achieve “incredibly good first page load speeds that are hard to match using pure client-side integration techniques”¹.

Astro, EcmaScript Modules and React provides a straightforward interface to set it up.

const MicroFrontends = ({ manifest }) => {

const MicroFrontendA = React.lazy(
() => import(manifest.mfa.url)
);

const MicroFrontendB = React.lazy(
() => import(manifest.mfb.url)
);

return (
<>
<MicroFrontendA />;
<MicroFrontendB />;
</>
};

The composition is written in React but it could easily be written in another framework like Solid. Note that this only works with the Deno adapter since remote modules are behind an experimental flag in Node.

Caching

Deno is a simple, modern runtime for JavaScript and TypeScript, “a runtime that resembles the web, using browser APIs that work on the server”. One benefit of using Deno in this solution is that remote modules are cacheable.

By default, “a module in the cache will be reused without fetching or re-compiling it”². This means that we don’t need any logic to implement caching, it just works — with zero lines of code.

Contracts

Each micro-frontend hosts a contract that tells the Shell application where to locate assets. It takes form of a manifest that Vite easily can generate, including hash codes for each asset.

Unique names to asset files is going to bust Deno’s cache, maintaining independent deployment of micro-frontends. Consequently we can write this in the Shell to retrieve manifests and structure them.

const manifestA = await fetchManifest("https://www.mfa.com/manifest.json");
const manifestB = await fetchManifest("http://www.mfb.com/manifest.json");

return {
mfa: {
url: `http://www.mfa.com/${manifestA[js][asset]}`,
css: `http://www.mfa.com/${manifestA[css][asset]}`,
},
mfb: {
url: `http://www.mfb.com/${manifestB[js][asset]}`,
css: `http://www.mfb.com/${manifestB[css][asset]}`,
},
};

Service discovery

Amazon has done a lot of great work on Frontend service discovery. Essentially it details how to apply the service discovery pattern to a micro-frontend architecture.

Service discovery is usually implemented with a Load Balancer for backend applications, but a micro-frontend Composer (i.e Shell app) needs a unified manifest with entry points for assets.

Frontend service discovery is not needed to SSR micro-frontends with Astro (a manifest generated by Vite might be adequate), but for large organisations it can simplify the architecture, improve performance and reduce design time coupling between applications.

Conclusion

Server-side rendering of micro-frontends is a powerful mechanism that allows us to have higher performance and better SEO. With Astro, Deno and ESM it’s actually possible to have several micro-frontends on a page and send zero kilobytes of JavaScript to the browser.

Interested in trying this out? I’ve written an example without Frontend service discovery on GitHub.

Footnotes

  1. Michael Geers, Micro Frontends in Action (Manning Publications, 2020)
  2. See the Deno documentation on Reloading modules

--

--