frameworkEdit this page

Micro-frontends

Micro-frontends are a way to split your application into smaller, more manageable pieces. Each piece is a separate application that can be developed, tested, and deployed independently. This allows you to scale your development team and infrastructure more easily.

Micro-frontends are a great way to scale your application development. They allow you to:

Warning: this is a highly experimental feature! Use with caution.

You can use any apps built with @lazarv/react-server as micro-frontends. Any route in your app can be a micro-frontend. Still you need to implement your micro-frontend route to be able to work as an outlet. You can't use an html document. You just need to render an HTML fragment. The most simple way to do this is to render a paragraph for example:

export default function MicroFrontend() {
  return (
    <p>
      This is a micro-frontend!
    </p>
  );
}

Just don't use html, head or body tags in your micro-frontend route as the hosting application can't render multiple of these tags.

Note: React Server Components, client components and server actions are all supported in micro-frontends when using the @lazarv/react-server framework using server-side rendering.

@lazarv/react-server provides a set of tools to help you implement micro-frontends in your application. You can use the RemoteComponent component to load a micro-frontend from a remote URL. This allows you to load a micro-frontend on demand and render it in your application using server-side rendering.

The RemoteComponent component takes a src prop that specifies the URL of the micro-frontend. By using this component, you can compose your application from multiple micro-frontends, each developed and deployed independently.

import { RemoteComponent } from "@lazarv/react-server/router";

export default function Home() {
  return (
    <div>
      <h1>Hosting application</h1>
      <RemoteComponent src="http://localhost:3001" />
    </div>
  );
}

When you exported the source of the RemoteComponent as static at build time using the remote flag the component still works as expected. The RemoteComponent will be replaced with the static content of the micro-frontend. Read more about how to export static micro-frontend content in the Static generation section of the documentation.

You can also export your hosting application while using the RemoteComponent as static at build time. The RemoteComponent will be replaced with the static content of the micro-frontend. So both your micro-frontend application and your hosting application can be statically generated at build time to achieve the best performance when using static content in a micro-frontend architecture.

For streaming response to work from a micro-frontend, you need to pass defer as a prop to the RemoteComponent. The initial content will be rendered during server-side rendering, and the rest of the content will be streamed from the micro-frontend after the initial content is rendered and the RemoteComponent gets hydrated.

import { RemoteComponent } from "@lazarv/react-server/router";

export default function Home() {
  return (
    <div>
      <h1>Hosting application</h1>
      <RemoteComponent src="http://localhost:3001" defer />
    </div>
  );
}

It's also possible to use the ReactServerComponent component with the url and defer props to load a micro-frontend from a remote URL. This component is similar to the RemoteComponent component, but it only renders the micro-frontend on the client side. This is very similar to how Astro server islands work with the server:defer attribute. Learn how outlets work in the router section of the documentation about client-side navigation.

import { ReactServerComponent } from "@lazarv/react-server/navigation";

export default function Home() {
  return (
    <div>
      <h1>Hosting application</h1>
      <ReactServerComponent url="http://localhost:3001" defer />
    </div>
  );
}

When you use micro-frontends, you need to share dependencies between the hosting application and the micro-frontends. You can use import maps to specify the shared dependencies between the hosting application and the micro-frontends.

An import map is a JSON file that specifies the mapping between the import specifier and the URL of the module. You can use the importMap option in the react-server.config.json file to specify the import map for your application.

{
  "importMap": {
    "imports": {
      // ...
    }
  }
}

When you specify the import map in the react-server.config.json file in each app, the hosting application and the micro-frontends will share the specified dependencies.

To externalize the shared dependencies, you can use the resolve.shared option in the react-server.config.json file. This option allows you to specify the shared dependencies that should be loaded from a remote URL.

{
  "resolve": {
    "shared": [
      "react",
      "react/jsx-dev-runtime",
      "react/jsx-runtime",
      "react-dom",
      "react-dom/client",
      "react-server-dom-webpack/client.browser",
    ]
  }
}

When you specify the shared dependencies in the react-server.config.json file, the hosting application and the micro-frontends will load the shared dependencies from the specified remote URL.

For @lazarv/react-server to work properly with import maps, you need to specify the source for react, react-jsx/runtime (or react/jsx-dev-runtime), react-dom, react-dom/client, and react-server-dom-webpack/client.browser in the import map.

If you want to use these dependencies from the CDN even during development, you can specify the source for these dependencies in the import map, but keep in mind that you need the development version of these dependencies.

Warning: You need to use the exact same versions of react, react/jsx-dev-runtime, react/jsx-runtime, react-dom, react-dom/client, and react-server-dom-webpack/client.browser in the hosting application and the micro-frontends, both on the client and the server side. Otherwise, you may encounter compatibility issues.

An example import map that works both in development and production while using React from esm.sh is shown below:

export default {
  importMap: {
    imports: {
      ...(process.env.NODE_ENV !== "production"
        ? {
            react:
              "https://esm.sh/[email protected]?dev",
            "react/jsx-dev-runtime":
              "https://esm.sh/[email protected]/jsx-dev-runtime?dev",
            "react-dom":
              "https://esm.sh/[email protected]?dev",
            "react-dom/client":
              "https://esm.sh/[email protected]/client?dev",
            "react-server-dom-webpack/client.browser":
              "https://esm.sh/[email protected]/client.browser?dev",
            "http://localhost:3001/": "/",
            "http://localhost:3003/": "/",
          }
        : {
            react:
              "https://esm.sh/[email protected]",
            "react/jsx-runtime":
              "https://esm.sh/[email protected]/jsx-runtime",
            "react-dom":
              "https://esm.sh/[email protected]",
            "react-dom/client":
              "https://esm.sh/[email protected]/client",
            "react-server-dom-webpack/client.browser":
              "https://esm.sh/[email protected]/client.browser",
            "http://localhost:3003/client/node_modules/@lazarv/react-server/":
              "/client/node_modules/@lazarv/react-server/",
          }),
    },
  },
  resolve: {
    shared: [
      "react",
      "react/jsx-dev-runtime",
      "react/jsx-runtime",
      "react-dom",
      "react-dom/client",
      "react-server-dom-webpack/client.browser",
    ],
  },
};

For a fully working example of micro-frontends, check out the micro-frontends example in the @lazarv/react-server repository.

To run the example, clone the @lazarv/react-server repository and run the following commands in the root directory:

pnpm install
pnpm --filter ./examples/remote dev