Microfrontend Routing Strategies
Why does routing become challenging in a microfrontend setup?
Microfrontend Routing Strategies

Why does routing become challenging in a microfrontend setup?
In a basic application, we usually have a single router. But when switching to a microfrontend architecture, if each microfrontend wants to implement its own router, who should control window.history?
The problem is this:
1 — User wants to go /orders/43
2 — Shell decides which remote must load. /orders/ (Orders fetched from MF)
3 — The Orders microfrontend is being loaded — but here’s the big question: how will the remote know about the /42 part of the URL? Should it have its own router? Who is responsible for parsing the URL?
Why window.history important?
BrowserRouter call window.history.pushState()in back. This is a native function that can change navigation history. If two BrowserRouter instances run at the same time, both of them may call these history APIs, which can lead to race conditions, corrupted navigation history, and unpredictable back button behavior.
1. Shell level routing.
In this type of routing, the shell application is aware of all routes and decides which remote should be rendered for a given URL. The remote itself doesn’t own the routing logic — it behaves more like a regular component.”
// webpack.config.js — Shell (Host)
new ModuleFederationPlugin({
name: 'shell',
remotes: {
orders: 'orders@http://localhost:3001/remoteEntry.js',
profile: 'profile@http://localhost:3002/remoteEntry.js',
}
})
// Shell App.jsx — All routing is in here
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { lazy, Suspense } from 'react'
const OrdersApp = lazy(() => import('orders/App'))
const ProfileApp = lazy(() => import('profile/App'))
export default function App() {
return (
<BrowserRouter>
<Suspense fallback="Loading...">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/orders" element={<OrdersApp />} />
<Route path="/profile" element={<ProfileApp />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
export default function OrdersApp() {
// Bu component sadece render eder
// URL'den haberi yok, alt route'ları yok
return <div>Orders list is here</div>
}
When we use this type of routing:
- If we have small teams. Time is more important than complexity.
- Every remote has 1 page.
2. MemoryRouter routing.
MemoryRouter keeps navigation history entirely in its memory. It never updates the browser URL. A remote can navigate internally, but the browser is unaware of those route changes. The shell doesn’t touchwindow.history, and the remote doesn’t either. This provides full isolation.”
// Shell App.jsx
<Routes>
<Route path="/orders/*" element={<OrdersApp />} />
</Routes>
// Shell only know "/orders" prefix
// It not need to now other parts (necessary part)
// Orders MF — App.jsx
import { MemoryRouter, Routes, Route } from 'react-router-dom'
export default function OrdersApp({ initialPath = '/list' }) {
return (
<MemoryRouter initialEntries={[initialPath]}>
<Routes>
<Route path="/list" element={<OrderList />} />
<Route path="/:id" element={<OrderDetail />} />
</Routes>
</MemoryRouter>
)
}
Critical Issue: Deeplink does not work.
User sends/orders/42 to his friend. His friend opened it, and what happened:
1 — The browser goes to /orders/42. Shell understands it is OrdersApp, and it renders OrdersApp.
2 — OrdersApp starts with MemoryRouter. If the initialPath prop is not provided, it starts with the default /list route.
3 — The user can not see the #42 order
Solution: pass initialPath from the shell.
// Shell - parses the part of the URL after /orders
function OrdersWrapper() {
const location = useLocation() // /orders/42
const subPath = location.pathname.replace('/orders', '')
// subPath = "/42"
return <OrdersApp initialPath={subPath} />
}
// But this only runs once — after that, even if the URL changes, the remote won’t update
// That’s why MemoryRouter is more suitable for modals/widgets
3. Synchronize routing.
Synchronize routing; Isolation with basename
The basename prop in React Router tells it to only care about the part of the URL after a given prefix. The shell handles/orders/*, while the remote is used basename="/orders" to manage its own nested routes. The browser URL still reflects the real path, so deep linking works properly.
// Shell App.jsx
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/orders/*" element={<OrdersApp basePath="/orders" />} />
<Route path="/profile/*"element={<ProfileApp basePath="/profile" />} />
</Routes>
</BrowserRouter>
//Shell only send prefix as prop. clean interface
// Orders MF — App.jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
export default function OrdersApp({ basePath = '/orders' }) {
return (
<BrowserRouter basename={basePath}>
<nav>
<Link to="/list">All</Link>
{/* This link: /orders/list */}
</nav>
<Routes>
<Route path="/" element={<OrderList />} />
<Route path="/list" element={<OrderList />} />
<Route path="/:id" element={<OrderDetail />} />
</Routes>
</BrowserRouter>
)
}
// When the remote runs in standalone mode, the default basePath is used
// This allows it to be tested independently without being connected to the shell ← senior approach
How deeplink works now:
- URL: /orders/42 — the shell’s
BrowserRouterdetects the/orders/*route and renders theOrdersApp - The
OrdersAppis mounted withbasename="/orders". The remoteBrowserRouterreads the URL/orders/42, strips the basename, and resolves it as/42TheOrdersAppis mounted withbasename="/orders". The remoteBrowserRouterreads the URL/orders/42, strips the basename, and resolves it as/42 - The route
/:idmatches, soid = "42".OrderDetailis rendered, anduseParams()returns{ id: "42" }. Deep linking works! - The remote
BrowserRouternavigates to/list, and the browser URL becomes/orders/list. The shell does not intervene.

메타데이터
- post_id
- e66112fdbf7d
- slug
- microfrontend-routing-strategies-e66112fdbf7d
- url
- https://levelup.gitconnected.com/microfrontend-routing-strategies-e66112fdbf7d
- canonical_url
- https://levelup.gitconnected.com/microfrontend-routing-strategies-e66112fdbf7d
- author_url
- https://medium.com/@simuratli
- status
- ok
- fetched_at
- 2026-06-21 19:25:17