跪拜 Guibai
← All articles
Frontend

Front-End Routing in 20 Lines: How Hash Routing Works

By 凌涘 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Hash routing is still the default fallback when the History API isn't available, and understanding its 20-line core reveals the fundamental pattern behind every client-side router: intercept a URL change, look up a handler, and swap DOM. That pattern carries directly into React Router, Vue Router, and any framework's routing layer.

Summary

Before the History API, single-page applications faced a dilemma: changing the URL triggered a full page refresh, but keeping it static broke bookmarks and back-button navigation. Hash routing resolved this by exploiting the browser's existing anchor-link behavior. The fragment after `#` never gets sent to the server, yet modifying it updates the address bar and creates a history entry. A `hashchange` event listener detects these changes and swaps DOM content accordingly. The entire mechanism fits into a HashRouter class of about 20 lines: a key-value routing table maps hash paths to render callbacks, and `location.hash.slice(1)` strips the leading `#` for clean lookups. The approach is a textbook case of repurposing existing infrastructure — anchor links meant for in-page scrolling became the foundation of client-side navigation. Its obvious downsides are the permanent `#` in URLs, zero SEO value since the fragment never reaches the server, and poor compatibility with server-side rendering. HTML5's History API later solved these by allowing full path changes without reloads, but the core principle — intercept navigation and replace content with JavaScript — remains identical.

Takeaways
Changing the hash part of a URL never sends a request to the server and never triggers a page reload, but it does update the address bar and browser history.
A HashRouter needs only three pieces: a routing table (hash-to-callback map), a `hashchange` listener, and a fixed DOM mount point for content replacement.
`location.hash` includes the leading `#`, so `.slice(1)` keeps route keys clean and consistent during registration and lookup.
The `hashchange` event callback's `this` defaults to `window`; `.bind(this)` fixes it to the router instance so `this.routers` remains accessible inside `load()`.
Hash routing's limitations — ugly `#` in URLs, no SEO, SSR difficulties — are what the HTML5 History API (`pushState`/`replaceState` + `popstate`) was designed to overcome.
Conclusions

The hash routing pattern is a clean example of repurposing existing browser behavior rather than waiting for a purpose-built API. Anchor links were designed for in-page scrolling, but their side effects — no server request, history entry creation — turned out to be exactly what client-side routing needed.

The entire concept scales down to a key-value lookup triggered by an event. That simplicity explains why hash routing appeared in countless early SPA frameworks and why it remains a viable fallback when `pushState` isn't available.

Concepts & terms
Hash / Fragment
The portion of a URL after the `#` symbol. Originally designed for anchor links that scroll to a specific page section, it is never sent to the server and can be modified without triggering a page reload.
hashchange event
A browser event that fires whenever the URL's hash fragment changes, whether from a user clicking an anchor link, editing the address bar, or JavaScript modifying `location.hash`. It provides the trigger mechanism for hash-based routing.
History API
An HTML5 API (`pushState`, `replaceState`, and the `popstate` event) that allows JavaScript to modify the full URL path without a page refresh, eliminating the need for the `#` fragment used in hash routing.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗