URL Hash Fragment Routing Guide

May 28, 20266 min read

Everything after the # in a URL is the hash fragment — and it occupies a unique position in web architecture. Unlike the path and query string, the fragment is never sent to the server. It exists entirely within the browser, which makes it the original mechanism for client-side routing in single-page applications (SPAs). Understanding how fragment routing works helps you debug navigation issues, implement anchor links, and choose between hash-based and history-based routing.

How Hash Fragments Work

When a browser loads https://example.com/page#section-2, it:

  1. Sends a request for https://example.com/page (the fragment #section-2 is omitted)
  2. Renders the page
  3. Scrolls to the element with id="section-2"

If JavaScript later changes the fragment via location.hash = 'section-3', the browser updates the URL and scrolls to the new anchor — but it does not make a new server request.

The HashChangeEvent

Browsers fire a hashchange event whenever the fragment changes, whether from user navigation or JavaScript:

window.addEventListener('hashchange', (event) => {
  console.log('Old:', event.oldURL);
  console.log('New:', event.newURL);
});

This event is the foundation of hash-based routing: your JavaScript listens for hashchange and re-renders the appropriate view.

Hash-Based Routing in SPAs

Before the History API, every SPA used hash fragments for routing. The pattern looks like this:

https://example.com/#/dashboard
https://example.com/#/users/42
https://example.com/#/settings?tab=security

The #/ prefix signals that the fragment contains a route path rather than an anchor ID. Here is a minimal router:

const routes = {
  '/dashboard': renderDashboard,
  '/users/:id': renderUserProfile,
  '/settings': renderSettings,
};

function navigate() {
  const path = location.hash.slice(1); // Remove #
  const handler = matchRoute(routes, path);
  if (handler) handler();
}

window.addEventListener('hashchange', navigate);
navigate(); // Initial render

Advantages of Hash Routing

AdvantageWhy
No server configurationThe server only sees the base URL — no need for catch-all rewrites
Works on static hostingFragment routes never generate 404s because the server always serves the same HTML
Broad browser supporthashchange works in every browser including IE8
Preserves back/forwardBrowser history tracks fragment changes automatically

Disadvantages of Hash Routing

DisadvantageWhy
Ugly URLs/#/users/42 is less clean than /users/42
Fragments are not sent to serverServer-side analytics and logging lose route information
SEO limitationsSearch engines historically ignored fragments (now partially indexed but still suboptimal)
Anchor conflictYou cannot distinguish between #section-id (anchor) and #/route (page) without a convention

History-Based Routing (The Modern Alternative)

The History API (pushState, replaceState, popstate) enables routing without hash fragments:

history.pushState(null, '', '/users/42');
window.addEventListener('popstate', () => {
  renderRoute(location.pathname);
});

History routing produces clean URLs (/users/42), but it requires server-side configuration to return the SPA's HTML for all routes — otherwise a direct visit to /users/42 returns a 404.

FeatureHash RoutingHistory Routing
Server config neededNoYes (catch-all)
Clean URLsNo (/#/...)Yes (/...)
SEO-friendlyLimitedFull
Fragment trackingNot sent to serverFull path visible to server
Browser supportIE8+IE10+

When to Use Hash Routing

Hash routing remains the right choice in several scenarios:

  • Static hosting without rewrites — GitHub Pages, Netlify static drops, or S3 buckets where you cannot configure fallback routing
  • Embedded widgets — iframes or micro-frontends where you cannot control the host page's server
  • Email and SMS deep links — links where the server must always return a valid page, and the fragment determines client-side state
  • Progressive enhancement — applications that must work even if JavaScript fails to load (the server-rendered page always loads; the fragment adds SPA navigation)

If your app uses hash routing (#/route) but also needs traditional anchor links, you need a convention to disambiguate. A common pattern:

#/docs/installation#prerequisites

Some routers (like Vue Router's hash mode) support this by splitting the fragment at the second #. Others require you to use query parameters for anchors:

#/docs/installation?anchor=prerequisites

Key Takeaways

  • Hash fragments are never sent to the server, making them ideal for purely client-side routing.
  • hashchange events power hash-based routers without requiring server configuration.
  • History-based routing produces cleaner, more SEO-friendly URLs but requires server-side catch-all configuration.
  • Use hash routing for static hosting, embedded widgets, and scenarios where server configuration is out of your control.
  • When mixing hash routing with anchor links, adopt a consistent convention to distinguish routes from scroll targets.

Try It Yourself

Building or debugging a routed application? The URL Parser breaks any URL into its components — scheme, host, path, query parameters, and fragment — so you can verify that your routes are structured correctly.