Frontend Routing from Scratch: Hash, History, and the MPA-to-SPA Shift
Understanding that React Router is thin sugar over `hashchange` and `location.hash` turns framework routing from memorized incantation into a predictable tool. Developers who know the underlying event loop and `this`-binding mechanics debug navigation bugs faster and make better trade-offs between hash routing, history API routing, and SSR strategies.
Multi-page applications reload the entire document on every navigation, repeating 90% of the markup for a 10% content change and producing a jarring white flash. Single-page applications solve this by keeping one HTML shell and swapping only the content area, but they must still update the URL for bookmarking and back-button support. The hash fragment—`#/about`—changes the address bar without sending a new HTTP request, making it the foundation of client-side routing. A hand-rolled `HashRouter` class in vanilla JavaScript, wired to the `hashchange` event, demonstrates the mechanism before React Router's `HashRouter`, `Routes`, `Route`, `Link`, `useParams`, `Outlet`, and `lazy`-plus-`Suspense` code-splitting are introduced as declarative wrappers over the same primitives.
Frameworks feel like magic only when the underlying browser primitives are unknown; once `hashchange` and `location.hash` are understood, React Router becomes an obvious convenience rather than a black box.
The `this`-binding trap in `addEventListener` is a recurring source of bugs that many developers work around without understanding—explicitly teaching it as part of routing makes the lesson stick.
SPA's SEO and initial-load weaknesses are not solved by hash routing; they require SSR or pre-rendering, which the article acknowledges but does not implement, leaving a natural next step for the reader.