Article Guide
Senior13 min readJune 13, 2026

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.

Tags:
Micro FrontendsRoutingDeep LinkingFrontend ArchitectureSystem DesignInterview Prep

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:

textEditor
frontend-app
├── /products
├── /product/:id
├── /cart
├── /checkout
├── /profile
└── /orders

One app owns the route table.

In micro frontends, route ownership is distributed.

Example:

textEditor
Shell App
├── Catalog Remote
├── Product Details Remote
├── Cart Remote
├── Checkout Remote
├── Profile Remote
└── Orders Remote

Now the question becomes:

textEditor
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:

textEditor
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
/review

This 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:

textEditor
/                         → 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 Remote

The shell owns:

  • Top-level paths
  • Route-to-remote mapping
  • Auth guards
  • Layout selection
  • Fallback route
  • Remote loading
  • Route-level error boundaries

Example flow:

flow diagram
User opens /cart
      │
      ▼
Shell matches /cart
      │
      ▼
Shell loads Cart Remote
      │
      ▼
Cart Remote renders Cart Page

This is predictable and easy to explain in interviews.

4. Remote-Owned Routing

Remote-owned routing means the remote manages its own internal screens.

Example:

textEditor
Profile Remote
├── /profile/details
├── /profile/addresses
├── /profile/payment-methods
└── /profile/preferences

The shell does not need to know every internal profile page.

It only needs to know:

textEditor
/profile/* → Profile Remote

Then 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.

textEditor
Shell owns top-level route selection.
Remote owns nested route behavior.

Example:

textEditor
Shell:
/checkout/* → Checkout Remote

Checkout Remote:
/checkout/address
/checkout/delivery
/checkout/payment
/checkout/review

This model gives the best balance.

ConcernOwner
Top-level pathShell
Domain route subtreeRemote
Auth guardShell + Remote
Internal tabs/pagesRemote
Fallback routeShell
Domain redirectsRemote
Global navigationShell

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:

RouteOwnerRemote
/Shell/HomeHome Remote
/categories/:slugCatalog TeamCatalog Remote
/searchSearch TeamSearch Remote
/product/:idProduct TeamProduct Details Remote
/cartCart TeamCart Remote
/checkout/*Checkout TeamCheckout Remote
/profile/*Profile TeamProfile Remote
/orders/*Orders TeamOrders Remote
/campaign/:slugMarketing TeamMarketing 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:

flow diagram
User opens /products
      │
      ▼
Shell route matcher
      │
      ▼
Load Catalog Remote
      │
      ▼
Render Catalog page

Runtime loading sequence:

sequence diagram
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 renders

This 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:

textEditor
https://example.com/categories/shoes?page=2&sort=price

This 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.

flow diagram
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 page

Important 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:

textEditor
/profile/addresses

the 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:

flow diagram
Refresh /profile/addresses
      │
      ▼
Server returns shell index.html
      │
      ▼
Shell loads Profile Remote
      │
      ▼
Profile Remote renders Addresses page

If server fallback is missing:

flow diagram
Refresh /profile/addresses
      │
      ▼
404 Not Found

That 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:

textEditor
/categories/shoes?sort=price&page=2

The shell knows:

textEditor
/categories/:slug → Catalog Remote

The 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

textEditor
/checkout/address
/checkout/delivery
/checkout/payment
/checkout/review

The shell should usually map:

textEditor
/checkout/* → Checkout Remote

Then 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:

textEditor
Catalog Remote → Product Details Remote
Product Details Remote → Cart Remote
Cart Remote → Checkout Remote

Recommended 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:

textEditor
/products
/product/123
/cart
/checkout

Back button should go:

textEditor
/checkout → /cart → /product/123 → /products

To 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:

flow diagram
User opens /orders
      │
      ▼
Shell checks session
      │
      ├── not logged in → redirect to /login?redirect=/orders
      │
      └── logged in → load Orders Remote

Remote 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:

textEditor
User opens /checkout
Not logged in
Redirect to /login?redirect=/checkout
After login
Return to /checkout

This 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:

textEditor
Catalog Remote claims /products/*
Product Remote also claims /products/:id

This 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:

jsonEditor
{
  "/": {
    "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-PatternWhy It Is Bad
Shell knows every internal remote screenShell becomes too coupled
Remotes claim overlapping routesRoute conflicts
No refresh fallbackDeep links break
State kept only in memoryShared links fail
Query params parsed by shell for all domainsShell becomes domain-aware
Direct remote-to-remote navigation importsCoupling
No route registryOwnership confusion
No auth redirect preservationBad user experience
Client-only rendering for SEO-critical pagesPoor discoverability
No route-level monitoringDebugging 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:

textEditor
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)