Routing and Deep Linking in Micro Frontends
Learn how to design routing in micro frontend architecture, including shell-owned routes, remote-owned nested routes, deep linking, refresh handling, route conflicts, auth guards, SEO, and interview tradeoffs.
Routing is one of the most important design decisions in micro frontend architecture.
A micro frontend system can have independent teams, separate remotes, and runtime loading, but if routing is not designed properly, the user experience quickly breaks.
Common routing failures include:
- Direct URLs do not work.
- Page refresh breaks the remote.
- Two micro apps claim the same route.
- The shell knows too much about remote internals.
- Remote apps cannot manage nested pages.
- Auth redirects behave inconsistently.
- Browser back/forward navigation breaks.
A strong micro frontend routing design should support:
- Top-level route ownership
- Nested route ownership
- Deep linking
- Refresh safety
- Auth guards
- Fallback routes
- Route-level remote loading
- SEO where needed
- Clear team ownership
This article explains how to design routing and deep linking in micro frontends from beginner level to senior frontend architect level.
1. Why Routing Is Hard in Micro Frontends
In a normal frontend monolith, routing is usually centralized.
Example:
frontend-app
├── /products
├── /product/:id
├── /cart
├── /checkout
├── /profile
└── /ordersOne app owns the route table.
In micro frontends, route ownership is distributed.
Example:
Shell App
├── Catalog Remote
├── Product Details Remote
├── Cart Remote
├── Checkout Remote
├── Profile Remote
└── Orders RemoteNow the question becomes:
Who owns /products?
Who owns /profile/addresses?
Who owns /checkout/payment?
Who handles auth redirect?
Who handles refresh?
Who loads the right remote?Without clear route ownership, micro frontends become chaotic.
2. Core Routing Principle
The safest routing principle is:
The shell owns top-level routing. Remotes own nested routing inside their domain boundary.
Example:
Shell owns:
/products
/product/:id
/cart
/checkout
/profile
/orders
Profile Remote owns:
/profile/details
/profile/addresses
/profile/preferences
Checkout Remote owns:
/checkout/address
/checkout/delivery
/checkout/payment
/reviewThis gives both control and autonomy.
The shell knows which remote to load.
The remote controls its internal domain flow.
3. Shell-Owned Routing
In shell-owned routing, the shell decides which remote should load for a route.
Example route map:
/ → Home Remote
/categories/:categorySlug → Catalog Remote
/search → Search Remote
/product/:productId → Product Details Remote
/cart → Cart Remote
/checkout → Checkout Remote
/profile/* → Profile Remote
/orders/* → Orders RemoteThe shell owns:
- Top-level paths
- Route-to-remote mapping
- Auth guards
- Layout selection
- Fallback route
- Remote loading
- Route-level error boundaries
Example flow:
User opens /cart
│
▼
Shell matches /cart
│
▼
Shell loads Cart Remote
│
▼
Cart Remote renders Cart PageThis is predictable and easy to explain in interviews.
4. Remote-Owned Routing
Remote-owned routing means the remote manages its own internal screens.
Example:
Profile Remote
├── /profile/details
├── /profile/addresses
├── /profile/payment-methods
└── /profile/preferencesThe shell does not need to know every internal profile page.
It only needs to know:
/profile/* → Profile RemoteThen the Profile Remote decides what to render.
Benefits:
- Domain team owns its own navigation.
- Shell stays simple.
- Remote can add internal pages without shell release.
- Route ownership is clear.
Risk:
- If remotes define routes carelessly, route conflicts can happen.
So route prefixes must be governed.
5. Hybrid Routing Model
Most production micro frontend systems use a hybrid model.
Shell owns top-level route selection.
Remote owns nested route behavior.Example:
Shell:
/checkout/* → Checkout Remote
Checkout Remote:
/checkout/address
/checkout/delivery
/checkout/payment
/checkout/reviewThis model gives the best balance.
| Concern | Owner |
|---|---|
| Top-level path | Shell |
| Domain route subtree | Remote |
| Auth guard | Shell + Remote |
| Internal tabs/pages | Remote |
| Fallback route | Shell |
| Domain redirects | Remote |
| Global navigation | Shell |
Strong interview phrase:
The shell should know which domain owns a route, but it should not need to know every internal screen of that domain.
6. Route Ownership Table
For an e-commerce platform:
| Route | Owner | Remote |
|---|---|---|
| / | Shell/Home | Home Remote |
| /categories/:slug | Catalog Team | Catalog Remote |
| /search | Search Team | Search Remote |
| /product/:id | Product Team | Product Details Remote |
| /cart | Cart Team | Cart Remote |
| /checkout/* | Checkout Team | Checkout Remote |
| /profile/* | Profile Team | Profile Remote |
| /orders/* | Orders Team | Orders Remote |
| /campaign/:slug | Marketing Team | Marketing Remote |
This table should be documented.
Without route ownership documentation, routing conflicts become common.
7. Route-Level Remote Loading
Routes often map to remote loading.
Example:
User opens /products
│
▼
Shell route matcher
│
▼
Load Catalog Remote
│
▼
Render Catalog pageRuntime loading sequence:
User opens /profile/addresses
│
▼
Shell loads
│
▼
Shell matches /profile/*
│
▼
Shell fetches profile remoteEntry.js
│
▼
Profile Remote loads
│
▼
Profile Remote matches /profile/addresses
│
▼
Address Book page rendersThis keeps remote loading lazy and route-driven.
8. Deep Linking
Deep linking means a user can directly open a URL and land on the correct page.
Example:
https://example.com/categories/shoes?page=2&sort=priceThis should work even if the user:
- Opens the URL in a new tab
- Refreshes the page
- Shares the link
- Bookmarks the link
- Navigates through browser history
A good micro frontend architecture must support deep linking.
Bad behavior:
/products works only if user navigated from homepage.
Good behavior:
/products works from direct URL, refresh, bookmark, and shared link.
9. Deep Linking Flow
Example: user directly opens /profile/addresses.
Browser requests /profile/addresses
│
▼
Server returns Shell App
│
▼
Shell reads current route
│
▼
Shell matches /profile/*
│
▼
Shell loads Profile Remote
│
▼
Profile Remote reads /profile/addresses
│
▼
Profile Remote renders Address Book pageImportant requirement:
The server or hosting layer must fallback to the shell app for client-side routes.
Otherwise refresh can return 404.
10. Refresh Handling
Refresh is one of the most common problems.
If the user refreshes:
/profile/addressesthe server must not look for a physical file at that path.
Instead, it should return the shell app.
Then the shell and remote handle routing in the browser.
Expected flow:
Refresh /profile/addresses
│
▼
Server returns shell index.html
│
▼
Shell loads Profile Remote
│
▼
Profile Remote renders Addresses pageIf server fallback is missing:
Refresh /profile/addresses
│
▼
404 Not FoundThat is a routing infrastructure bug.
11. URL State
URL state is useful for route-level state.
Examples:
- /categories/shoes?page=2&sort=price-low-to-high
- /search?q=laptop&brand=apple
- /orders?status=delivered
- /profile/preferences?tab=notifications
Good URL state:
- Search query
- Filters
- Sorting
- Pagination
- Selected tab
- Category slug
- Product ID
Bad URL state:
- Auth token
- Payment data
- Full cart object
- Sensitive user data
- Large JSON payload
- Private session state
URL state should be shareable, meaningful, and safe.
12. Query Params Ownership
The remote that owns the route should own the meaning of its query params.
Example:
/categories/shoes?sort=price&page=2The shell knows:
/categories/:slug → Catalog RemoteThe Catalog Remote owns:
- sort
- page
- filter
- brand
- priceRange
The shell should not parse every catalog filter.
That would make the shell too domain-aware.
Strong phrase:
The shell owns route selection; the remote owns route interpretation.
13. Nested Routes
Nested routes are common inside remotes.
Example: Checkout Remote
/checkout/address
/checkout/delivery
/checkout/payment
/checkout/reviewThe shell should usually map:
/checkout/* → Checkout RemoteThen Checkout Remote owns the internal step routing.
Benefits:
- Checkout team can change checkout flow.
- Shell does not need release for every step change.
- Domain logic stays inside checkout.
But for business-critical flows, route changes should still be governed and tested.
14. Navigation Between Micro Frontends
Navigation can happen between remotes.
Example:
Catalog Remote → Product Details Remote
Product Details Remote → Cart Remote
Cart Remote → Checkout RemoteRecommended approach:
- Use shell/router navigation contract.
- Avoid direct remote-to-remote imports.
- Use URLs as navigation boundaries.
Bad:
Catalog Remote imports ProductDetails internal navigation function.
Good:
Catalog Remote navigates to /product/123. Shell loads Product Details Remote.
This keeps remotes decoupled.
15. Browser Back and Forward
Browser navigation must work.
Example journey:
/products
/product/123
/cart
/checkoutBack button should go:
/checkout → /cart → /product/123 → /productsTo support this:
- Use browser history correctly.
- Avoid hidden in-memory route state.
- Keep route state in URL where appropriate.
- Ensure remotes integrate with shell router.
If each remote creates its own disconnected history incorrectly, browser navigation can break.
16. Auth Guards and Protected Routes
Some routes require authentication.
Examples:
- /profile
- /orders
- /checkout
Shell can own global auth protection.
Flow:
User opens /orders
│
▼
Shell checks session
│
├── not logged in → redirect to /login?redirect=/orders
│
└── logged in → load Orders RemoteRemote can own feature-level permission checks.
Example:
Orders Remote checks whether user can view this specific order.
Important:
Shell handles authentication bootstrap. Remotes and backend APIs still enforce authorization.
17. Redirect Handling
Redirects should preserve user intent.
Example:
User opens /checkout
Not logged in
Redirect to /login?redirect=/checkout
After login
Return to /checkoutThis is important for conversion.
Bad:
User opens /checkout Redirect to login After login goes to homepage Cart flow is lost
Good:
Return user to original route after successful login.
Redirect logic should be consistent across remotes.
The shell is usually the right place for global redirect rules.
18. Route Conflicts
Route conflicts happen when two remotes claim the same path.
Example:
Catalog Remote claims /products/*
Product Remote also claims /products/:idThis creates ambiguity.
Avoid with:
- Central route registry
- Route ownership documentation
- Route naming conventions
- CI checks for duplicate routes
- Platform review for new top-level routes
Good ownership:
/categories/* → Catalog Remote /product/:id → Product Details Remote
Bad ownership:
/products/* → multiple remotes
19. Route Registry
A route registry documents route ownership.
Example:
{
"/": {
"owner": "home-team",
"remote": "homeApp"
},
"/categories/*": {
"owner": "catalog-team",
"remote": "catalogApp"
},
"/product/:id": {
"owner": "product-team",
"remote": "productApp"
},
"/cart": {
"owner": "cart-team",
"remote": "cartApp"
},
"/checkout/*": {
"owner": "checkout-team",
"remote": "checkoutApp"
}
}A route registry helps with:
- Ownership
- Documentation
- Conflict prevention
- Remote loading
- Monitoring
- SEO planning
- Testing
20. Fallback Routes
Fallback routes are important.
Types of fallback:
- 404 route
- Remote loading failure fallback
- Unauthorized fallback
- Deprecated route redirect
- Maintenance fallback
Examples:
- Unknown route → 404 page
- Cart remote fails → Cart unavailable fallback
- Unauthenticated user → login redirect
- Old route → redirect to new route
A shell should handle global fallback behavior.
Remotes can handle domain-specific fallback states.
21. SEO Considerations
SEO matters for public pages like:
- Home
- Category pages
- Product pages
- Marketing landing pages
- Content pages
SEO may be less important for:
- Cart
- Checkout
- Profile
- Orders
- Admin pages
If SEO is important, consider:
- Server-side rendering
- Static rendering
- Edge composition
- Metadata ownership
- Canonical URLs
- Structured data
- Fast LCP
Micro frontends can support SEO, but runtime-only client rendering may not be enough for all pages.
For e-commerce, category and product pages often need stronger SEO handling.
22. Route Metadata Ownership
Routes often need metadata:
- Title
- Description
- Canonical URL
- Open Graph tags
- Breadcrumbs
- Structured data
Ownership should follow domain.
Example:
- Catalog Remote owns category metadata.
- Product Remote owns product metadata.
- Marketing Remote owns campaign metadata.
The shell may provide the mechanism to set metadata, but the remote owns the domain data.
Rule:
Platform provides metadata infrastructure; domain remote provides metadata content.
23. Analytics and Routing
Route changes should be tracked consistently.
Shell can track:
- Page views
- Route transitions
- Remote loaded
- Remote load time
- Remote load failure
- Shell version
- Remote version
Remotes can track domain events:
- catalog:filter-applied
- product:add-to-cart-clicked
- cart:quantity-changed
- checkout:payment-submitted
This avoids inconsistent analytics across remotes.
24. Testing Routing
Routing must be tested.
Test cases:
- Direct open /profile/addresses works.
- Refresh /profile/addresses works.
- Back/forward navigation works.
- Auth redirect preserves original route.
- Unknown route shows 404.
- Remote load failure shows fallback.
- Nested remote route renders correctly.
- Route conflict checks pass.
- SEO metadata renders for public pages.
Testing deep links is especially important.
Do not test only navigation clicks from homepage.
25. Common Routing Anti-Patterns
| Anti-Pattern | Why It Is Bad |
|---|---|
| Shell knows every internal remote screen | Shell becomes too coupled |
| Remotes claim overlapping routes | Route conflicts |
| No refresh fallback | Deep links break |
| State kept only in memory | Shared links fail |
| Query params parsed by shell for all domains | Shell becomes domain-aware |
| Direct remote-to-remote navigation imports | Coupling |
| No route registry | Ownership confusion |
| No auth redirect preservation | Bad user experience |
| Client-only rendering for SEO-critical pages | Poor discoverability |
| No route-level monitoring | Debugging route issues is hard |
26. Interview Questions
Q1. Who should own routing in micro frontends?
Usually the shell owns top-level routing, while each remote owns nested routing inside its domain. This prevents route conflicts while allowing domain teams to control their internal navigation.
Q2. How do you support deep links?
The server should fallback to the shell app for client-side routes. The shell should match the top-level route, load the correct remote, and the remote should interpret its nested path and query parameters.
Q3. How do you avoid route conflicts?
Use a central route registry, route ownership documentation, naming conventions, and CI checks to prevent multiple remotes from claiming the same route.
Q4. How should URL query params be handled?
The remote that owns the route should usually own the meaning of its query params. For example, Catalog Remote should interpret filter, sort, and pagination params for category pages.
Q5. How do you handle protected routes?
The shell should handle authentication bootstrap and global route guards. Remotes should handle feature-level authorization, and backend APIs must enforce real authorization.
Q6. How do you handle navigation between remotes?
Use URL-based navigation through the shell/router. A remote should navigate to a route like /product/123 or /cart, and the shell should load the appropriate remote. Avoid direct imports between remotes.
27. Strong Senior Answer
If an interviewer asks:
“How would you design routing in a micro frontend architecture?”
A strong answer:
I would use a hybrid routing model. The shell would own top-level routes and decide which remote to load. For example, /categories/* loads the Catalog Remote, /cart loads the Cart Remote, and /checkout/* loads the Checkout Remote. Inside each domain, the remote would own nested routing. For example, the Checkout Remote can own /checkout/address, /checkout/payment, and /checkout/review. For deep linking, the server should always return the shell app for client-side routes. Then the shell matches the route, loads the correct remote, and the remote renders the correct nested page. Query params like filters, sorting, and pagination should be owned by the remote that owns the route. I would also maintain a route registry to avoid conflicts, use auth guards in the shell for protected routes, preserve redirect URLs after login, and test direct URL access, refresh, browser back/forward, and fallback behavior. The key principle is that the shell owns route selection, while remotes own route interpretation.
28. Final Routing Checklist
- Does the shell own top-level routes?
- Do remotes own nested routes?
- Is every route mapped to one owner?
- Is there a route registry?
- Do deep links work?
- Does refresh work?
- Does browser back/forward work?
- Are query params owned by the correct remote?
- Are protected routes guarded?
- Does login redirect preserve original URL?
- Are route conflicts prevented?
- Are unknown routes handled?
- Are remote loading failures handled?
- Is SEO considered for public routes?
- Are route transitions observable?
29. Summary
Routing in micro frontends should be simple, explicit, and ownership-driven.
Recommended model:
Shell owns top-level routing.
Remotes own nested domain routing.
URLs are the boundary between domains.
Deep links and refresh must work.
Query params belong to the route owner.
Protected routes need shell auth and remote/backend authorization.The strongest takeaway:
The shell should own route selection, and the remote should own route interpretation.
This keeps routing predictable without turning the shell into a domain-aware monolith.
References
- Micro Frontends — Martin Fowler (https://martinfowler.com/articles/micro-frontends.html)
- Micro Frontends (https://micro-frontends.org)
- webpack Module Federation Documentation (https://webpack.js.org/concepts/module-federation/)
- AWS Prescriptive Guidance: Micro-frontends (https://docs.aws.amazon.com/prescriptive-guidance/latest/micro-frontends-aws/introduction.html)
- Module Federation Official Site (https://module-federation.io)