Skip to main content
HomeWritingPhotography

Menu

HomeWritingPhotography

Socials

GitHubLinkedInX (Twitter)Mail

Legal

ImpressumPrivacy PolicyTerms of Use

© 2026 All rights reserved

Made in Germany

Who owns the URL?

Shell routing, remote routing, and hybrid models for Module Federation microfrontends, and how React Bridge changes the default.

Every microfrontend setup eventually asks the same question: who owns the browser URL?

Get it wrong and you get double mounts, broken back buttons, duplicated layouts, or remotes that only work when embedded, not when bookmarked.

There is no universal best answer. There are three common models. The right one depends on how independent your teams (and remotes) really are.

Three models#

Shell-owned     Shell owns every route. Remotes fill slots.
Remote-owned    Shell mounts a prefix. Remote owns the subtree.
Hybrid          Shell owns the top. Remotes own deep prefixes.
Loading diagram…

Shell-owned#

The host owns the router. Remotes render into route slots. They do not register top-level routes.

/                 -> Shell + Dashboard remote
/orders           -> Shell + Orders list remote
/orders/:id       -> Shell + Order detail remote

Works when you want one product surface: shared nav, auth guards, analytics, deep links.

Cost: the shell team becomes the coordinator for new top-level routes.

Remote-owned#

Each remote ships its own router subtree. The shell mounts it at a prefix and gets out of the way.

/billing/*        -> Billing remote owns everything under /billing
/support/*        -> Support remote owns everything under /support

Works when remotes were standalone apps before federation, or teams need full autonomy over nested navigation.

Cost: inconsistent URL patterns, harder global guards, more trailing-slash edge cases.

Hybrid#

Shell owns product-wide routes. Remotes own deep subtrees under delegated prefixes.

/                     -> shell
/orders               -> shell + orders list
/orders/:id/*         -> orders remote owns the rest
/admin/*              -> admin remote owns subtree

This is the pragmatic enterprise pattern: constrain what must be consistent, delegate what can vary.

How Module Federation fits#

Routing choice tracks how you cut the remote, not which bundler you use.

CutTypical routingTooling
Granular React componentsShell-ownedExpose widgets; shell wires routes
Whole apps (application-level modules)Remote-owned or hybridReact Bridge

With component-level remotes, shell-owned routing is natural. The host already owns composition, so it should own the URL map too.

With application-level remotes (a remote that is basically a full app with its own router), prefer remote-owned or hybrid. That is what @module-federation/bridge-react is for: export an app with createBridgeComponent, load it with createRemoteAppComponent, and let Bridge coordinate basename + router context. See also exporting and loading apps. Modern.js wires the same Bridge APIs through its Module Federation integration.

// Host: mount an application remote under a prefix
<Route path="/billing/*" element={<BillingApp basename="/billing" />} />

Inside the remote, Bridge can inject basename so nested routes stay under /billing without fighting the shell history.

Three traps#

  1. Double layouts: shell wraps chrome, remote also renders chrome. Pick one owner.
  2. Broken back button: two routers fighting history. Prefer one history source; with Bridge, remotes consume parent router context instead of spinning up a second BrowserRouter.
  3. Auth in the wrong layer: shell guards protect top-level access; remote guards still matter for feature permissions and standalone deep links.

Quick pick#

NeedLikely model
One cohesive productShell-owned
Maximum team autonomy / former standalone appsRemote-owned
Shared top nav + deep team freedomHybrid

My default: start shell-owned. It forces clarity early (one URL map, one layout, one auth story).

When a team hits real limits (large, fast-moving subtree), delegate a prefix and move to hybrid. Full remote-owned routing is powerful, but it is also the easiest way to accidentally build four separate products that happen to share a domain.

Routing is not an implementation detail. It is the contract users feel every time they click a link.

Previous

DIY software factory with Cursor

Inspired by Zach Lloyd's software factory posts — a DIY take on squeezing more out of subscriptions you already pay for, with GitHub, Cursor, OpenCode, or whatever CLI fits the job.

Next

AI coding agents: trying everything, babysitting everything

Field notes on Cursor, Codex, Copilot, T3 Code, and the CI/review gap that still makes parallel agents feel like unpaid work.