Micro Frontends with Native Federation 🍿
Tags: webdev, javascript, tutorial, frontend
Published: 2024-04-01 10:24:43

*Photo by <a href="https://unsplash.com/@domjtalbot?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Dom Talbot</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-figurine-in-a-field-of-grass-ZuFxrKGzAj4?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>*

One of the things that I find interesting is how much the browsers evolved in the last couple of years. Surely, beforehand we also had quite some progress going on - but now  it's just so much detail work that goes into it. Among these many tiny improvements lures the support for native modules (EcmaScript modules - short ESM) and their capability of having aliases for modules in form of the importmaps standard.

A technology that uses this combination for providing support for micro frontends is "Native Federation". In this article I'd like to show what Native Federation is and what you need to get started with it.

👉 Find the code for this article [on GitHub](https://github.com/piral-samples/piral-cloud-native-federation-demo).

## What is Native Federation

Native Federation is a compile-time and runtime mechanism to allow different scripts (micro frontends) to share dependencies and expose / import components between each other. It builds upon the ideas of Module Federation.

According to its author, it comes with the following features:

- ✅ Mental model of Module Federation
- ✅ Future proof: Independent of build tools like Webpack and frameworks
- ✅ Embraces importmaps -- an emerging browser technology -- and ECMAScript modules (ESM)
- ✅ Easy to configure
- ✅ Blazing fast: The reference implementation not only uses the fast esbuild; it also caches already built shared dependencies (like Angular itself). However, as mentioned above, feel free to use it with any other build tool.

It works using the following mental model:

- **Remote**: The remote is a separately built and deployed application. It can expose ESM that can be loaded into other applications.
- **Host**: The host loads one or several remotes on demand. For your framework's perspective, this looks like traditional lazy loading. The big difference is that the host doesn't know the remotes at compilation time.
- **Shared Dependencies**: If a several remotes and the host use the same library, you might not want to download it several times. Instead, you might want to just download it once and share it at runtime. For this use case, the mental model allows for defining such shared dependencies.

In particular, this means that if two or more applications use a different version of the same shared library, Native Federation makes it possible to prevent a version mismatch. There are several strategies, like falling back to another version that fits the application, using a different compatible one (according to semantic versioning) or throwing an error, which are in place to mitigate this issue.

As mentioned, in general this is all very similar to Module Federation. Let's compare the two.

## Comparison to Module Federation

Native Federation builds upon the ideas and philosophy of Module Federation. However, in contrast to some popular believe these two are incompatible to each other.

There are other aspects, too, where both technologies are different:

| Area | Module Federation | Native Federation |
| ---- | ----------------- | ----------------- |
| Tooling | Webpack, rspack | Vite, esbuild    |
| Dependencies | Build-Time  | Build-Time      |
| Micro Frontends | Build & Runtime | Runtime  |
| Nested | Possible         | Not directly     |
| Support manifest | Not directly | Yes        |
| On the fly updates | Possible | Not possible |
| Module format | Independent | ESM            |

The most crucial difference is that Native Federation is not directly dependent on tooling support. As a downside there is no possibility of directly importing code from a remote module, e.g., the following code would not work:

```js
import('remote/module').then(({ Component }) => {
  // Use the imported Component
});
```

In contrast, in Native Federation we need to use the `loadRemoteModule` function imported from the `@softarc/native-federation` package:

```js
loadRemoteModule({
  remoteName: "remote",
  exposedModule: "./module",
}).then(({ Component }) => {
  // Use the imported Component
});
```

As already mentioned Native Federation is not directly dependent on tooling, even though shared dependencies must be resolved via the tooling. To do this Native Federation uses so-called adapters, which can be created for pretty much every bundler. Right now, the two best-supported bundlers are Vite and esbuild.

Module Federation was originally only available on Webpack. As of today, it is also available in rspack (quite naturally, as rspack tries to implement as many of the Webpack APIs as possible) and also via community contributions in other bundlers such as Vite and Rollup.

Technically, Native Federation is strongly dependent on ESM as module format. Module Federation, on the other hand, is quite independent of the format - as long as the runtime is able to load more chunks (or modules). Usually, this implies a Webpack runtime and its own loading mechanisms, but actually what mechanism is really used is independent of Module Federation.

The dependence on ESM comes with some constraints. Most importantly, Native Federation makes use of importmaps to tell the micro frontends what dependencies are used and how these can be shared. However, since importmaps follow the "initialization is finalization" approach it is impossible to update them. This implies that micro frontends can - in general - not be updated on the fly. Otherwise, any update of a micro frontend would need to work with the already shared / constructed list of dependencies in the importmap.

Finally, the `@softarc/native-federation` package is bundled with each micro frontend. As the package is rather small this is not directly an issue, however, it becomes an issue regarding accessing globally loaded micro frontends. We'll see now in an example application how to mitigate / improve this.

## Example Application

To actually see what we can do with Native Federation we'll build a simple example application. Naturally, I've picked the [famous Tractor shop by Michael Geers](https://micro-frontends.org/) which is the ToDo-MVC of micro frontends.

In the end our application is supposed to look like this:

![Tractor shop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ma98mvj0zh9yyklnkdj2.png)

Importantly, the applied CSS design (red dashes, blue dashes, green dashes) is used to make the boundaries and origins of the respective UI fragments clear.

The whole application is also fully interactive, requiring the individual micro frontends to communicate, e.g., to align on a chosen tractor:

![Tractor shop in action](https://res.cloudinary.com/practicaldev/image/fetch/s--W01zHLDG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ioof54k742n5pxpv90p.gif)

With this in mind we have the following distribution of UI fragments:

- **app shell**: The entry point, used to load and display the page component from **red**.
- **red**: Contains the components from the Red team - just a single page (tractor details page). This page includes three more fragments; 2 from **blue** and 1 from **green**.
- **blue**: Contains the components from the Blue team - a component to display the shopping cart symbol with a counter, and a buy button component.
- **green**: Contains the components from the Green team - just a single component being used as recommended products with respect to the currently selected product.

What do we actually need for the app shell to get started? We start by scaffolding a new npm project and installing the required dependencies:

```sh
npm init -y
npm install native-federation-esbuild @module-federation/vite vite typescript --save-dev
npm install @softarc/native-federation --save
```

Now we create a file *index.html* with the following content:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Tractor Store</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/index.ts"></script>
  </body>
</html>
```

This assumes that our code will run in a browser with ESM and importmap support. If this assumption is not correct we should add the following part to the `<head>`:

```html
<script type="esms-options">
{
  "shimMode": true
}
</script>
<script src="https://ga.jspm.io/npm:es-module-shims@1.5.17/dist/es-module-shims.js"></script>
```

With this set up it's time to look at the *src/index.ts*:

```js
import "./style.css";
import { initFederation, loadRemoteModule } from "@softarc/native-federation";

(async () => {
  await initFederation({
    "red": "http://localhost:2001/remoteEntry.json",
    "blue": "http://localhost:2002/remoteEntry.json",
    "green": "http://localhost:2003/remoteEntry.json",
  });

  await loadRemoteModule({
    remoteName: "red",
    exposedModule: "./productPage",
  }).then(({ renderProductPage }) => {
    const root = document.querySelector("#app");
    renderProductPage(root);
  });
})();
```

This gets the basic styling from the *style.css*. The actual bootstrapping is then performed using the `initFederation` function. Afterwards, we can use the `loadRemoteModule` to obtain the exposed product page from the **red** micro frontend. At this point everything is supposed to be served locally.

Now let's continue with the first micro frontend - the **red** micro frontend.

Moving to a new / fresh directory we start scaffolding the foundation. We'll need a new npm project with the right dependencies:

```sh
npm init -y
npm i @hyrious/esbuild-plugin-style @module-federation/vite esbuild-auto-path-plugin native-federation-esbuild typescript vite @types/react @types/react-dom --save-dev
npm i @softarc/native-federation react react-dom --save
```

As you'll see we also install React to write the components of this micro frontend in React. Surely, Native Federation is completely independent of such frameworks and we could also choose something else.

Concerning the actual component code we create a new file `product-page.tsx` in the `src` folder with the following content:

```ts
import "./style/product-page.css";
import React from "react";
import ReactDOM from "react-dom";
import { loadRemoteModule } from "@softarc/native-federation";
import tractorRed from "./images/tractor-red.jpg";
import tractorBlue from "./images/tractor-blue.jpg";
import tractorGreen from "./images/tractor-green.jpg";
import tractorRedThumb from "./images/tractor-red-thumb.jpg";
import tractorBlueThumb from "./images/tractor-blue-thumb.jpg";
import tractorGreenThumb from "./images/tractor-green-thumb.jpg";

const product = {
  name: "Tractor",
  variants: [
    {
      sku: "porsche",
      color: "red",
      name: "Porsche-Diesel Master 419",
      image: tractorRed,
      thumb: tractorRedThumb,
      price: "66,00 €",
    },
    {
      sku: "fendt",
      color: "green",
      name: "Fendt F20 Dieselroß",
      image: tractorGreen,
      thumb: tractorGreenThumb,
      price: "54,00 €",
    },
    {
      sku: "eicher",
      color: "blue",
      name: "Eicher Diesel 215/16",
      image: tractorBlue,
      thumb: tractorBlueThumb,
      price: "58,00 €",
    },
  ],
};

const BasketInfo = React.lazy(() => loadRemoteModule({
  remoteName: "blue", exposedModule: "./basketInfo",
}));
const BuyButton = React.lazy(() => loadRemoteModule({
  remoteName: "blue", exposedModule: "./buyButton",
}));
const ProductRecommendations = React.lazy(() => loadRemoteModule({
  remoteName: "green", exposedModule: "./recommendations",
}));

function getCurrent(sku: string) {
  return product.variants.find((v) => v.sku === sku) || product.variants[0];
}

const ProductPage = () => {
  const [sku, setSku] = React.useState("porsche");
  const current = getCurrent(sku);

  return (
    <React.Suspense fallback="Loading ...">
      <h1 id="store">The Model Store</h1>
      <div className="blue-basket" id="basket">
        <BasketInfo sku={sku} />
      </div>
      <div id="image">
        <div>
          <img src={current.image} alt={current.name} />
        </div>
      </div>
      <h2 id="name">
        {product.name} <small>{current.name}</small>
      </h2>
      <div id="options">
        {product.variants.map((variant) => (
          <button
            key={variant.sku}
            className={sku === variant.sku ? "active" : ""}
            type="button"
            onClick={() => setSku(variant.sku)}
          >
            <img src={variant.thumb} alt={variant.name} />
          </button>
        ))}
      </div>
      <div className="blue-buy" id="buy">
        <BuyButton sku={sku} />
      </div>
      <div className="green-recos" id="reco">
        <ProductRecommendations sku={sku} />
      </div>
    </React.Suspense>
  );
};

export default ProductPage;

export function renderProductPage(container: HTMLElement) {
  ReactDOM.render(<ProductPage />, container);
}
```

Importantly, we do not only export the component directly (as the default export - to make it already compatible with `React.lazy`), but also a function to render it inside some container (in this case `renderProductPage`). The latter is necessary to work across frameworks - or to just mount the root node as we've seen in the app shell.

Now, we'll also build the other (**blue** and **green**) micro frontends. Like with the **red** one, we scaffold them using `npm init -y`. However, the code inside is different.

For the **green** micro frontend we have a single component / file that will be exposed (*product-recommendations.tsx*, placed in a *src* folder):

```ts
import "./style/recommendations.css";
import React from "react";
import ReactDOM from "react-dom";
import reco1 from "./images/reco_1.jpg";
import reco2 from "./images/reco_2.jpg";
import reco3 from "./images/reco_3.jpg";
import reco4 from "./images/reco_4.jpg";
import reco5 from "./images/reco_5.jpg";
import reco6 from "./images/reco_6.jpg";
import reco7 from "./images/reco_7.jpg";
import reco8 from "./images/reco_8.jpg";
import reco9 from "./images/reco_9.jpg";

const recos = {
  1: reco1,
  2: reco2,
  3: reco3,
  4: reco4,
  5: reco5,
  6: reco6,
  7: reco7,
  8: reco8,
  9: reco9,
};

const allRecommendations = {
  porsche: ["3", "5", "6"],
  fendt: ["3", "6", "4"],
  eicher: ["1", "8", "7"],
};

const Recommendations = ({ sku = "porsche" }) => {
  const recommendations = allRecommendations[sku] || allRecommendations.porsche;

  return (
    <>
      <h3>Related Products</h3>
      {recommendations.map((id) => (
        <img src={recos[id]} key={id} alt={`Recommendation ${id}`} />
      ))}
    </>
  );
};

export default Recommendations;

export function renderRecommendations(container: HTMLElement) {
  ReactDOM.render(<Recommendations />, container);
}
```

For the **blue** micro frontend we have two components. Each will be in its own file.

First, let's create a file *basket-info.tsx* in a *src* folder:

```ts
import "./style/basket-info.css";
import React from "react";
import ReactDOM from "react-dom";

const BasketInfo = ({ sku = "porsche" }) => {
  const [items, setItems] = React.useState([]);
  const count = items.length;

  React.useEffect(() => {
    const handler = () => {
      setItems((items) => [...items, sku]);
    };
    window.addEventListener("add-item", handler);
    return () => window.removeEventListener("add-item", handler);
  }, [sku]);

  return (
    <div className={count === 0 ? "empty" : "filled"}>basket: {count} item(s)</div>
  );
};

export default BasketInfo;

export function renderBasketInfo(container: HTMLElement) {
  ReactDOM.render(<BasketInfo />, container);
}
```

Then, for the second component, create a file *buy-button.tsx* in the *src* folder:

```ts
import "./style/buy-button.css";
import React from "react";
import ReactDOM from "react-dom";

const defaultPrice = "0,00 €";
const prices = {
  porsche: "66,00 €",
  fendt: "54,00 €",
  eicher: "58,00 €",
};

const BuyButton = ({ sku = "porsche" }) => {
  const price = prices[sku] || defaultPrice;

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        window.dispatchEvent(new CustomEvent("add-item", { detail: price }));
      }}
    >
      <button>buy for {price}</button>
    </form>
  );
};

export default BuyButton;

export function renderBuyButton(container: HTMLElement) {
  ReactDOM.render(<BuyButton />, container);
}
```

For brevity, I've skipped the CSS and image files. Please have a look at the sample repository for all of them; needless to say the CSS files are rather straight forward.

The CSS of the `BuyButton`, for instance, looks as follows:

```css
#buy {
  align-self: center;
  grid-area: buy;
}

#buy button {
  background: none;
  border: 1px solid gray;
  border-radius: 20px;
  cursor: pointer;
  display: block;
  font-size: 20px;
  outline: none;
  padding: 20px;
  width: 100%;
}

#buy button:hover {
  border-color: black;
}

#buy button:active {
  border-color: seagreen;
}

.blue-buy {
  display: block;
  outline: 3px dashed royalblue;
  padding: 5px;
}
```

Nothing too fancy here.

Having written the whole code it's time to let it run. But how? After all, this is where we need to integrate with Native Federation. Let's start with the **green** micro frontend as its standalone (like **blue**), but only a single component (unlike **blue**).

For this, we create a file *vite.config.ts* with the following content:

```ts
import { defineConfig } from "vite";
import { style } from "@hyrious/esbuild-plugin-style";
import { autoPathPlugin } from "esbuild-auto-path-plugin";
import { federation } from "@module-federation/vite";
import { createEsBuildAdapter } from "native-federation-esbuild";

export default defineConfig(async ({ command }) => ({
  plugins: [
    await federation({
      options: {
        workspaceRoot: __dirname,
        outputPath: "dist",
        tsConfig: "tsconfig.json",
        federationConfig: "src/federation.ts",
        verbose: false,
        dev: command === "serve",
      },
      adapter: createEsBuildAdapter({
        plugins: [autoPathPlugin(), style()],
      }),
    }),
  ],
}));
```

This file is placed in the package's root folder (e.g., *green*), i.e., adjacent to the *package.json*.

So what's happening in this file? Let's dissect it:

- We define a build configuration to be used by Vite, i.e., for the micro frontend to become a standalone application
- By using the `federation` function from Module Federation we extract part of the application into a side-bundle, which is built using a different toolset
- The different toolset is using the provided adapter `createEsBuildAdapter`, which is using esbuild
- The esbuild configuration also comes with some plugins; one plugin for using styles and another plugin to transform paths (e.g., to assets such as images) dynamically at runtime; this is important as we don't know yet where / how the micro frontends are hosted and which paths they take in different environments (e.g., production environment)

Crucially, the specific mechanisms of the `federation` helper / plugin options are determined by another re-usable file called *federation.ts*:

```ts
const {
  withNativeFederation,
  shareAll,
} = require("@softarc/native-federation/build");

module.exports = withNativeFederation({
  name: "green",
  exposes: {
    "./recommendations": "./src/product-recommendations.tsx",
  },
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: "auto",
      includeSecondaries: false,
    }),
  },
});
```

Here, we export a configuration that super similar to a configuration of Webpack Module Federation. Specifically, we set the name of the micro frontend (**green**), the exposed modules via `exposes` and the shared dependencies via `shared`. For the latter we use the `shareAll` helper, which takes the `dependencies` from the *package.json* and transforms them into shared dependencies.

At this point the green micro frontend can run standalone; at least when we also create an *index.html* as we did for the app shell.

For this micro frontend we can use the following HTML boilerplate:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <script type="esms-options">
    {
        "shimMode": true
    }
  </script>
  <script src="https://ga.jspm.io/npm:es-module-shims@1.5.17/dist/es-module-shims.js"></script>
  <meta charset="UTF-8" />
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Green Standalone</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>
```

where *main.ts* is the entry module of the application:

```ts
import { initFederation } from "@softarc/native-federation";

(async () => {
  await initFederation({});
  await import("./bootstrap");
})();
```

Doing the same for **blue** we end up with a bunch of micro frontends - all capable of delivering of a full application when combined. This is where we hit the first rock.

![We hit some disgusting output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ti1h3z5p0r9ll9q4wr7i.png)

It turns out that only the application initializing Native Federation can set what micro frontends exist. However,

- from perspective of our main application only **red** exists
- only within **red** we know that **green** and **blue** are needed

This puts us in a problematic situation. Even worse: As `@softarc/native-federation` must be non-shared (after all, within the application it is used as a standard script; otherwise it cannot set/manipulate the import map before the first ESM loads) every micro frontend has its own version of it. Therefore, even setting *all* micro frontends within the application (**red**, **green**, **blue**) we are still unable to access **green** and **blue** from **red** (remember, red has its own version of the package, which was not initialized / does not know these micro frontends - they are only available from the root level / main application).

So how can we improve the situation? We can introduce a little helper, that solves this elegantly:

```ts
import { lazy } from "react";
import { initFederation, loadRemoteModule } from "@softarc/native-federation";

export async function setup(manifest?: string | Record<string, string>) {
  await initFederation(manifest);

  window.loadComponent = (remoteName, exposedModule) =>
    lazy(() =>
      loadRemoteModule({
        remoteName,
        exposedModule,
      })
    );
}

declare global {
  interface Window {
    loadComponent(remoteName: string, modulePath: string): React.FC<any>;
  }
}
```

This is a shared code in the sense that every micro frontend can use and embed it, but its not shared in the sense that every runtime needs to access the same instance of this code. In fact, every micro frontend will bundle this code in its standalone entry module, but not in its exposed modules.

With this we can replace the calls to `initFederation` with `setup` and calls to `loadRemoteModule` with `loadComponent` from `window`.

In the **red** micro frontend this looks like this (first, let's have a look at *main.ts*):

```ts
import { setup } from "@shared/loader";

(async () => {
  await setup({
    green: "http://localhost:2003/remoteEntry.json",
    blue: "http://localhost:2002/remoteEntry.json",
  });
  await import("./bootstrap");
})();
```

Again, this is only relevant in the standalone mode. However, the *product-page.tsx* was also impacted:

```ts
import "./style/product-page.css";
import React from "react";
import ReactDOM from "react-dom";

// no import of `@softarc/native-federation` any more!

// data etc. remains unchanged

const BasketInfo = window.loadComponent("mf-blue", "./basketInfo");
const BuyButton = window.loadComponent("mf-blue", "./buyButton");
const ProductRecommendations = window.loadComponent(
  "mf-green",
  "./recommendations"
);

// rest as before
```

So we now go against a globally available loader - which we are sure must be provided because at the end of the day we are running in an application.

The other great thing that this wrapper allows us is to change / extend the micro frontends easily, i.e., to introduce a micro frontend discovery service.

## Scaling with Micro Frontend Discovery

Just using Native Federation alone would quite acceptable, however, we want to take this to the next level. What we need is a Micro Frontend Discovery service that allows publishing new micro frontends or updating existing micro frontends without any friction.

Actually, using the [Piral Feed Service](https://portal.piral.cloud/) we can already do that. Remember that the `initFederation` function also accepts a string / URL? We can just use this:

```js
import './style.css';
import { initFederation } from "@softarc/native-federation";

(async () => {
  await initFederation('https://native-federation-demo.my.piral.cloud/api/v1/native-federation');
  await import("./bootstrap");
})();
```

In this example we use our own feed ("native-federation-demo") with the `native-federation` representation. Fetching data from this URL will result in the following payload:

```json
{
  "mf-blue": "https://assets.piral.cloud/pilets/native-federation-demo/mf-blue/1.0.0/remoteEntry.json",
  "mf-green": "https://assets.piral.cloud/pilets/native-federation-demo/mf-green/1.0.0/remoteEntry.json",
  "mf-red": "https://assets.piral.cloud/pilets/native-federation-demo/mf-red/1.0.3/remoteEntry.json"
}
```

If you want to follow these steps you will also most likely need to create your own feed. For this, you'll need log into [feed.piral.cloud](https://feed.piral.cloud/) and click on "+" (Create Feed):

![Creating a new micro frontend feed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gp0916bjwbbmsyhdzb3b.png)

After this, you should be redirected to a page with the feed details. Here, you can copy the URL for the native federation manifest representation (there are other options, too, but this one is the easiest format and thus works for us / this article).

The rest can remain the same. Now the question is: How can we upload these micro frontends?

To publish our micro frontends we can use the `publish-microfrontend` package. This is a simple command line tool that makes it possible to publish a micro frontend using a single command:

```sh
npx publish-microfrontend --url https://native-federation-demo.my.piral.cloud/api/v1/pilet --interactive
```

In this example the `--url` flag tells the CLI utility which service (and in this case: feed) to use. Generally, you could any service as long as it speaks the appropriate protocol - in this case a form POST request using the tarball with the micro frontend's assets.

The `--interactive` flag tells the CLI to use the interactive flow (if available) from the provided API. This will open a web browser redirecting to the login page for the Piral Feed Service. Alternatively, we could have created an API key and use it via the `--api-key` flag.

Once all micro frontends are published you should see the following in the feed overview:

![Micro frontends in the feed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nn93qmj9e2nzrxhng9l8.png)

And with this we are done - now everything works natively and can scale, too!

## Conclusion

Native Federation is an interesting technology if we know how to handle the existing edge cases and downsides as compared to Module Federation. Presumably, the strongest downside are the ones inherited from being on ESM - requiring support for importmaps and being constraint to the rules of importmaps such as initialization is finalization.

By using a micro frontend discovery service we can actually bring our solution to the next level - ensuring productivity and efficiency to make our solution not only adequate for the moment, but also in the future.

👉 Find the code for this article [on GitHub](https://github.com/piral-samples/piral-cloud-native-federation-demo).

In case of questions or general advice don't hesitate to connect with me on [LinkedIn](https://www.linkedin.com/in/florian-rappl/) 🙏.