Search code examples
reactjsmicro-frontendnx.dev

Multiple react micro front ends on one page


We're using nx to generate react applications, which we then use as micro front ends. Previously they have all been confined to separate pages and have worked flawlessly.

Recently we've added an application that is meant to be included on all pages (using layout.cshtml), and this causes issues. When two applications are to be rendered on the same page, two things happen:

  1. The first application renders, but no events (click etc.) trigger, making it unusable.
  2. The second application does not get rendered at all.

No errors are shown in the browser console in the deployed environment.

When trying to reproduce the problem, the following error is thrown only if runtime.js is included more than once on a page:

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

This leads me to believe it's an issue with us including react multiple times on the page. I've started looking into adding react to peer dependencies for the applications. Since we have been including react in the main dependencies in package.json. However, I cannot figure out how it's supposed to work in our case, since the applications are built completely separate from one another.

Is there a way to make this work? I am imagining somehow adding all common dependencies to a single file and including it on every page, instead of including them all in every application.

html:

...

<div id="app1"></div>

<script src="/dist/apps/app1/runtime.js" type="text/javascript"/>
<script src="/dist/apps/app1/polyfills.esm.js" type="text/javascript"/>
<script src="/dist/apps/app1/main.esm.js" type="text/javascript"/>

...

<div id="app2"></div>

<script src="/dist/apps/app2/runtime.js" type="text/javascript"/>
<script src="/dist/apps/app2/polyfills.esm.js" type="text/javascript"/>
<script src="/dist/apps/app2/main.esm.js" type="text/javascript"/>

...

Solution

  • Of course I found the solution shortly after posting. The issue is detailed here: link

    tl;dr: the webpackJsonp object, which is added globally in each react app, needs to have a unique name for this to work.

    So i had to do the following:

    1. Add a custom webpack config (webpack.extra.js in this case) to the application
    2. reference the file in the application configuration in workspace.json.

    workspace.json snippet:

    ...
    "app2": {
      ...
      "targets": {
        "build": {
         ...
         "options": {
           ...
           "webpackConfig": "apps/app2/webpack.extra.js"
         },
         ...
    

    webpack.extra.js:

    const { merge } = require('webpack-merge');
    
    module.exports = (config, context) => {
      return merge(config, {
        output: {
          jsonpFunction: 'uniqueAppName',
        },
      });
    };