Skip to content

Commit 5e36dc2

Browse files
committed
Added routing communication in both ways
1 parent f0ea638 commit 5e36dc2

6 files changed

Lines changed: 249 additions & 11 deletions

File tree

learnings/MFE-Routing.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
🔹 Microfrontend Routing Requirements (Explained)
2+
3+
1. Both the Container + Individual SubApps need routing features
4+
5+
- The container app handles top-level navigation (e.g.,` /auth`, `/dashboard`).
6+
7+
- Each sub-app may also have its own routes (`/auth/login`, `/auth/signup`).
8+
9+
- Both must coexist without clashing.
10+
11+
2. Sub-apps might need to add in new pages/routes all the time
12+
13+
- Teams should be able to add or update routes inside their own microfrontend without modifying the container.
14+
15+
- Example: Marketing team adds `/pricing` → should work without container changes.
16+
17+
3. We might need to show two or more microfrontends at the same time
18+
19+
- Sometimes, the container needs to render multiple MFEs together (e.g., Dashboard showing `Analytics + Notifications`).
20+
21+
- Routing should allow parallel rendering.
22+
23+
4. We want to use off-the-shelf routing solutions
24+
25+
- Instead of building custom routing logic, we rely on libraries like React Router, Angular Router, Vue Router.
26+
27+
- Each MFE can use the same routing library to simplify integration.
28+
29+
5. We need navigation features for sub-apps in both hosted mode and in isolation
30+
31+
- Sub-app must work standalone (`npm start`) with routing.
32+
33+
- Sub-app must also work inside the container (`npm run serve`) without breaking.
34+
35+
6. If different apps need to communicate about routing, it should be generic
36+
37+
- Avoid hardcoding "container tells sub-app X".
38+
39+
- Use events or history API so MFEs remain independent.
40+
41+
- Example: If user navigates in container → sub-apps should respond via shared history object.
42+
43+
Routing in MFE:
44+
45+
```ascii
46+
+------------------+
47+
| Container |
48+
| (Global Routes) |
49+
+------------------+
50+
/ | \
51+
/ | \
52+
v v v
53+
+----------------+ +----------------+ +----------------+
54+
| SubApp A | | SubApp B | | SubApp C |
55+
| (/auth/*) | | (/dashboard/*) | | (/marketing/*) |
56+
+----------------+ +----------------+ +----------------+
57+
58+
[Features Needed]
59+
1. Container + SubApps both define routes
60+
2. SubApps can add new routes freely
61+
3. Multiple SubApps can render at same time
62+
4. Use off-the-shelf routers (React Router, etc.)
63+
5. SubApps work standalone & inside container
64+
6. Routing communication must be generic
65+
```
66+
67+
🧩 Problem Setup
68+
69+
- Container App
70+
71+
- Has a header with a logo → supposed to redirect to `/`.
72+
73+
- No routing logic in container (just renders sub-apps).
74+
75+
- Marketing App
76+
77+
- Has its own routes:
78+
79+
- `/` → Landing Page
80+
81+
- `/pricing` → Pricing Page
82+
83+
- What happens now?
84+
85+
- You navigate to `/pricing` (shows Pricing page ✅).
86+
87+
- You click Logo in Container → container changes URL to `/`.
88+
89+
- But UI still shows Pricing (❌), because Marketing App’s router didn’t notice container’s navigation.
90+
91+
Why this happens?
92+
93+
Since container has no routing logic, it doesn’t mount/unmount MFEs based on route.
94+
95+
- Marketing is always mounted.
96+
97+
- React Router inside Marketing listens to its own history instance.
98+
99+
- But when Container changes URL → Marketing’s router doesn’t get updated (they are disconnected).
100+
101+
Solution
102+
103+
Yes – you do need routing logic at the container level (at least minimal).
104+
105+
Two Approaches:
106+
107+
1. Container controls which sub-app mounts (Recommended)
108+
109+
- Container uses its own router (React Router, Vue Router, etc.).
110+
111+
- Example:
112+
113+
- `/` → render Marketing (Landing)
114+
115+
- `/pricing` → render Marketing (Pricing)
116+
117+
- `/auth/*` → render Auth app
118+
119+
This way → when logo navigates to `/`, container re-renders Marketing at root route → React Router inside Marketing will naturally show Landing.
120+
121+
2. Share history between Container + SubApp (Advanced)
122+
123+
- Instead of container routing, pass down a shared `history` object to sub-apps.
124+
125+
- Container navigation (`/`) triggers same history object → Marketing router updates.
126+
127+
- This requires wiring React Router in both places to the same history.
128+
129+
Rule of Thumb
130+
131+
- If container should control which microfrontend shows up → container must have routing logic.
132+
133+
- If container is “dumb shell” and only loads one sub-app at a time → share history so both stay in sync.
134+
135+
🔧 Fix for Your Case
136+
137+
Since you said container has no routing but has a header with logo:
138+
139+
- Best Fix → Add minimal routing in container:
140+
141+
```tsx
142+
// ContainerApp.tsx
143+
import { BrowserRouter, Route, Routes } from "react-router-dom";
144+
import MarketingApp from "./MarketingApp";
145+
146+
export default function ContainerApp() {
147+
return (
148+
<BrowserRouter>
149+
<Header /> {/* Logo inside this navigates to "/" */}
150+
<Routes>
151+
<Route path="/*" element={<MarketingApp />} />
152+
</Routes>
153+
</BrowserRouter>
154+
);
155+
}
156+
```
157+
158+
Note: - in microfrontends we typically share either BrowserHistory or MemoryHistory, depending on how we want the container and child apps to sync.
159+
160+
ASCII Diagram for this particular project router setup:
161+
162+
```sql
163+
+---------------------------+
164+
| CONTAINER |
165+
| React Router (Browser) |
166+
| Controls real URL bar |
167+
+-------------+-------------+
168+
|
169+
-----------------------------------------------
170+
| |
171+
+-------------------------+ +-------------------------+
172+
| MARKETING | | AUTH |
173+
| React Router (Memory) | | React Router (Memory) |
174+
| Local navigation only | | Local navigation only |
175+
| Not changing URL bar | | Not changing URL bar |
176+
+-------------------------+ +-------------------------+
177+
178+
```
179+
180+
Why This Router Setup?
181+
182+
In a microfrontend (MFE) system:
183+
184+
- Container App is responsible for the overall application and controls the real browser URL. That’s why it uses Browser History (so the URL is synced, bookmarkable, reloadable).
185+
186+
- Sub-apps (Marketing, Auth, etc.) are mounted inside the container. If they also tried to use `BrowserHistory`, multiple apps could fight over the URL at the same time.
187+
188+
- To prevent this conflict, sub-apps use Memory History.
189+
190+
- MemoryHistory keeps routing changes local to the sub-app.
191+
192+
- When the container needs to know about navigation, the sub-app can “notify” the container.
193+
194+
- The container then decides if/when to update the actual browser history.
195+
196+
This way, we get a clean separation:
197+
198+
- Container = controls the real URL.
199+
200+
- Sub-apps = handle their own routing internally without breaking others.
201+
202+
**Benefits of this Approach**
203+
204+
- Avoids conflicts → Only one app (the container) controls the real URL.
205+
206+
- Isolation → Sub-apps can add/remove routes anytime without breaking container or other MFEs.
207+
208+
- Flexibility → Container can decide when to sync sub-app navigation with the real URL.
209+
210+
- Scalability → More MFEs can be added without changing routing strategy.

packages/container/config/webpack.dev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const devConfig = {
99
devServer:{
1010
port:8080,
1111
historyApiFallback: {
12-
index: 'index.html' // Fallback to index.html for SPA routing
12+
index: '/index.html' // Fallback to index.html for SPA routing
1313
}
1414
},
1515
plugins:[
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { mount } from "marketing/MarketingApp"; //marketing is remote module name and MarketingApp is the exposed module in marketing dev config
22
import React, { useRef, useEffect } from "react";
3+
import { useHistory } from "react-router-dom";
34

45
export default () => {
56
const ref = useRef(null);
7+
const history = useHistory();
68

79
useEffect(() => {
8-
mount(ref.current);
9-
});
10+
const {onParentNavigate} = mount(ref.current, {
11+
onNavigate: ({pathname:nextPathname}) => {
12+
const { pathname } = history.location;
13+
14+
if (pathname !== nextPathname){
15+
history.push(nextPathname);
16+
}
17+
}
18+
});
19+
history.listen(onParentNavigate);
20+
},[]);
1021

1122
return <div ref={ref} />;
1223
};

packages/marketing/config/webpack.dev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const devConfig = {
99
devServer:{
1010
port:8081,
1111
historyApiFallback: {
12-
index: 'index.html' // Fallback to index.html for SPA routing
12+
index: '/index.html' // Fallback to index.html for SPA routing
1313
}
1414
},
1515
plugins:[

packages/marketing/src/App.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import {Switch,Route, BrowserRouter} from "react-router-dom";
2+
import {Switch,Route, Router} from "react-router-dom";
33
import {StylesProvider, createGenerateClassName} from "@material-ui/core/styles";
44

55
import Landing from "./components/Landing";
@@ -9,17 +9,17 @@ const generateClassName = createGenerateClassName({
99
productionPrefix: "ma",
1010
})
1111

12-
export default () => {
12+
export default ({history}) => {
1313

1414
return (
1515
<div>
1616
<StylesProvider generateClassName={generateClassName}>
17-
<BrowserRouter>
17+
<Router history={history}>
1818
<Switch>
1919
<Route exact path="/" component={Landing} />
2020
<Route path="/pricing" component={Pricing} />
2121
</Switch>
22-
</BrowserRouter>
22+
</Router>
2323
</StylesProvider>
2424
</div>
2525
)

packages/marketing/src/bootstrap.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
import React from "react";
22
import ReactDOM from "react-dom";
3+
import {createMemoryHistory, createBrowserHistory} from "history";
34
import App from "./App";
45

56
// Mount function to start up the app
6-
const mount = (el) => {
7-
ReactDOM.render(<App/>, el);
7+
const mount = (el, { onNavigate,defaultHistory }) => {
8+
const history = defaultHistory || createMemoryHistory();
9+
10+
if (onNavigate){
11+
history.listen(onNavigate);
12+
}
13+
14+
ReactDOM.render(<App history={history}/>, el);
15+
16+
return {
17+
onParentNavigate({pathname: nextPathname}){
18+
const { pathname } = history.location;
19+
20+
if(pathname !== nextPathname){
21+
history.push(nextPathname);
22+
}
23+
}
24+
}
825
};
926

1027
// If we are in devlopment and in isolation,
@@ -13,7 +30,7 @@ const mount = (el) => {
1330
if (process.env.NODE_ENV === "development") {
1431
const devRoot = document.querySelector("#_marketing-dev-root");
1532
if (devRoot) {
16-
mount(devRoot);
33+
mount(devRoot,{defaultHistory: createBrowserHistory()} );
1734
}
1835
}
1936

0 commit comments

Comments
 (0)