skip to content

In a microfrontend shell application, what is the difference between path-based routing (e.g. example.com/checkout) and hash-based routing (e.g. example.com/#/checkout) for deciding which microfrontend to load?

level: juniorimportance: must knowfreq 70%

answer

  1. # never hits the server
  2. SPA fallback / catch-all rewrite
  3. pathname vs hash listener
  4. SEO + SSR need real paths
  5. ownership enforceable at CDN only for path routing

basics

~20 s

Path-based routing uses the real URL path and needs server/CDN config to serve the app for every path; hash-based routing puts the route after a # so the browser never asks the server for it, but it looks less clean and hurts SEO.

solid answer

~40 s

Path routing (`/checkout/cart`) treats the segment after the domain as the real address; the web server or CDN must be configured to return the shell's `index.html` for any such path (a catch-all/rewrite rule), after which client-side JS reads `window.location.pathname` to decide which microfrontend to mount. Hash routing (`/#/checkout/cart`) exploits the fact that browsers never send the fragment after `#` to the server, so any static host works with zero rewrite rules, but URLs are uglier, non-SEO-friendly, and don't work with server-side rendering per route. Most production shells (single-spa, Module Federation shells) default to path-based routing with proper server rewrites and reserve hash-based for embedded/legacy or iframe-constrained contexts.

go deeper

for a junior

Should correctly state that hash routing needs no server config while path routing does, and give the syntax difference. Doesn't need to discuss ownership or SSR implications.

for a middle

Should explain the SPA-fallback rewrite mechanism and name at least one concrete trade-off on each side (SEO/SSR for path; zero-config embeddability for hash).

for a senior

Should connect the choice to ownership enforcement — path prefixes can be routed at the CDN/edge to different origins per team; hash routing can't be enforced below the client JS layer.

for a principal

Should reason about this as a platform decision with organizational consequences: which choice lets teams deploy independently, be indexed independently, and enforce boundaries without trusting every other team's JS.

## What the shell is deciding A **microfrontend shell** (also called an app-shell, container, or root config) is the top-level application responsible for deciding, based on the current URL, which independently-built and independently-deployed microfrontend to fetch and mount into the page. That decision is made by a router living in the shell, and the two most common strategies for encoding "where am I" in the URL are **path-based routing** and **hash-based routing**. ## Path-based routing Path-based routing uses the actual URL path segment — `https://example.com/checkout/cart` — as the routing key. The shell's router (often a thin wrapper around the History API: `pushState`, `replaceState`, `popstate`) inspects `window.location.pathname`, matches it against a route table such as `/checkout/* -> checkout-mfe`, `/account/* -> account-mfe`, and mounts the matching bundle. Because the path is a real URL, every navigation has to resolve to something the web server or CDN can serve: - a `pushState` from an in-app link - a full page load - a bookmarked URL - a server-rendered link from an email That means the server (or edge/CDN rewrite rule) must be configured with a catch-all rule that returns the shell's `index.html` (or an SSR response) for any path that belongs to the app, rather than 404ing, because the actual routing decision happens client-side after the shell's JS boots. This is the same "SPA fallback" problem every single-page app has, just centered in the shell rather than one app. ## Hash-based routing Hash-based routing instead encodes the route after a `#`, e.g. `https://example.com/#/checkout/cart`. The browser treats everything after `#` as a same-document fragment identifier and — critically — **never sends it to the server** as part of the HTTP request. The server only ever sees a request for `/`. This means hash routing needs zero server-side rewrite configuration: any static file host, any CDN, any environment that can serve a single `index.html` works out of the box, because the server is oblivious to what's after the `#`. The shell's router instead listens to the `hashchange` event (or intercepts clicks and manually parses `location.hash`) to decide what to mount. ## The trade-off The trade-off is real and multi-dimensional. Path-based URLs: - are clean, and match user expectations of "real" URLs - are indexable by search engines and social-media link previews - support proper server-side rendering per route (the server can inspect the real path and render the right content before JS loads) Their cost is operational: every environment the app runs in — production CDN, preview deployments, local dev proxy, an embedding host if the shell is iframed — needs the SPA-fallback rewrite rule configured correctly, and misconfiguring it produces hard-to-debug 404s on deep-link refresh or on cold navigation from an external link. Hash-based URLs: - need no such configuration - are trivially embeddable (e.g., a microfrontend embedded inside a third-party CMS page where you don't control server routing at all) - but they are non-SEO-friendly and look unpolished to end users - and make server-side rendering per-route effectively impossible since the server never sees the fragment ## Ownership at the infrastructure layer In a microfrontend context specifically, this choice compounds with ownership: path-based routing lets you cleanly map a URL prefix to a team's deployable unit (`/checkout/*` is unambiguously "the checkout team's territory," enforceable at the CDN/reverse-proxy layer with routing rules that can even route different path prefixes to entirely different origins/servers — true edge-level microfrontend isolation). Hash-based routing collapses everything under one served document, so ownership boundaries can only be enforced in client-side JS, not at the infrastructure layer — any microfrontend's JS could in principle read or rewrite another's hash-route, and there's no server-side way to say "this MFE serves this business path." ## What mature platforms actually do In practice, most mature microfrontend platforms (single-spa's root-config, module-federation-based shells) default to path-based routing because it aligns URLs with business capability boundaries and supports SEO/SSR, accepting the operational cost of getting rewrite rules right in every environment. Hash-based routing survives mainly in legacy migrations, iframe-embedded widgets, or contexts where the team genuinely doesn't control server/CDN configuration.

  • Why does hash-based routing avoid the need for any server-side rewrite configuration?
    The browser never sends the fragment after `#` in the HTTP request line — the server only ever sees a request for the base document (e.g. `/`), so there's nothing for it to 404 on. The shell's JS reads `location.hash` client-side after the single document has already loaded, so no server route table needs to know about individual microfrontend paths.
  • What breaks if a CDN's SPA-fallback rewrite rule for a path-based shell is misconfigured?
    Deep links and hard refreshes on any route other than `/` return a real 404 from the server instead of the shell's index.html, because the server tries to resolve the path as a literal resource before the client-side router ever gets a chance to run. This typically shows up as 'the app works when I click around but breaks on refresh or when someone shares a link.'
  • Can you mix path-based and hash-based routing within the same microfrontend shell?
    Yes, though it's unusual and adds complexity: some platforms use path-based routing for the primary business routes (for SEO/SSR) and hash-based sub-routing inside a single mounted microfrontend for ephemeral UI state (a modal step, a tab) that shouldn't be a server-indexable route of its own.

Path-based routing is like a building with real street addresses each department can put on its own door and have mail delivered directly; hash-based routing is like everyone sharing one lobby address and using a handwritten note pinned inside the door to say which department you actually want.

saying these in an interview costs you the question

  • Says hash routing is purely 'the old way' with no operational trade-off reasoning
  • Doesn't mention the SPA-fallback/catch-all server rewrite requirement for path routing
  • Thinks the server 'sees' and can route on the hash fragment
  • Can't explain why hash routing needs no server config
  • Ignores the SEO/SSR implications entirely

context