Route conflict in micro-frontend system built with qiankun and the solution

  1. The main app
  2. The Sub App
  3. Start apps
  4. Test our app
  5. Solution

Qiankun is an implementation of Micro Frontends based on single-spa. It was developed by Ant Financial and has been widely used in many projects in Alibaba Group.

A Qiankun application consists of a main application and multiple sub applications. They each may have their own routes and this could lead to conflict in some cases.

In this post I am going to demonstrate a case of such a conflict and provide a solution for it.

At the time of writing, the latest version of qianukn is 2.10.16.

The main app

Create the main app and install dependencies:

1
2
3
npx create-react-app main-app
cd main-app
npm install qiankun react-router-dom@5

Configure qiankun in main-app/src/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { registerMicroApps, start } from "qiankun";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

registerMicroApps([
{
name: "micro-app",
entry: "//localhost:3001",
container: "#micro-container",
activeRule: "/app1",
},
]);

start();

Then add routes in main-app/src/App.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";

function App() {
return (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/app1">Micro App</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
<h2>About</h2>
</Route>
<Route path="/app1">
<div id="micro-container"></div>
</Route>
<Route path="/">
<h2>Home</h2>
</Route>
</Switch>
</Router>
);
}

export default App;

The Sub App

Next, it’s time to create a sub app, we are using create-react-app in this example.

1
2
3
npx create-react-app micro-app
cd micro-app
npm install react-router-dom@5

Note: We don’t need to install qiankun in our sub apps!

Then add routes configurations in micro-app/src/App.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";

function App() {
return (
<Router basename="/">
<nav>
<ul>
<li>
<Link to="/">Micro Home</Link>
</li>
<li>
<Link to="/page">Micro Page</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/page">
<h2>Micro Page</h2>
</Route>
<Route path="/">
<h2>Micro Home</h2>
</Route>
</Switch>
</Router>
);
}

export default App;

Add lifecycle functions in micro-app/src/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

let root = null;

function render(props) {
const { container } = props;
root = ReactDOM.createRoot(
container
? container.querySelector("#root")
: document.querySelector("#root")
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}

if (!window.__POWERED_BY_QIANKUN__) {
render({});
}

export async function bootstrap() {
console.log("micro-app bootstraped");
}

export async function mount(props) {
render(props);
}

export async function unmount(props) {
const { container } = props;
// If you are using older React
//ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
if (root) {
root.unmount();
}
}

In order to expose the sub app to the main app, additional configuration is needed for the build tool you are using. Suppose you are using webpack:

1
2
3
4
5
6
7
8
9
const packageName = require("./package.json").name;

module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: "umd",
jsonpFunction: `webpackJsonp_${packageName}`, // You may not need this line if the webpack > 5
},
};

In our case, there is no such file in the sub application because create-react-app encapsulates all the npm modules it uses internally, you’ll need to run npm run eject to get all configuration files.

Start apps

Run the following command to start the main app:

1
npm start

And run the same command again in the sub app, make sure your sub app is running on port 3001.

Test our app

Open http://localhost:3000 in your browser, you will see Home under the menu. Then, access http://localhost:3000/app1, and the sub app will be displayed at the bottom of the page with its own menus and content. So far, so good. However, when you click on the Micro Home link in the sub app, which points to /, the Home page of the main app is shown under the menus instead of what we expected. We were hoping to navigate to the home page of the sub app, not the main one.

In our case, both have a route / with different contents. When they are working together in a system, a problem occurs.

Solution

To solve this problem, we can use a basename in the route of the sub app.

micro-app/src/App.js

1
2
3
4
5
6
function App() {
return (
<Router basename="/app1">
</Router>
);
}

Or even better with a conditional check:

1
2
3
4
5
6
function App() {
return (
<Router basename={ window.__POWERED_BY_QIANKUN__ ? "/app1" : "/" }>
</Router>
);
}