跪拜 Guibai
← Back to the summary

Hash vs. History Routing: What Actually Happens in the Browser

What is routing? Routing is used to describe the path of resources on a server. This article helps us understand front-end routing.

1. Why Understand Front-End Routing?

In traditional multi-page applications, every page switch requires a new HTML request. If the user's network fluctuates significantly, the browser will display a blank screen while switching pages, forcing the user to wait several seconds for rendering. Single-page applications (SPAs) do not refresh the page; instead, they only switch components.

How is a single-page application implemented? Let's use vanilla JS to build a simple one:

<body>
    <header>
        <nav>Home</nav>
        <nav>Hot Topics</nav>
    </header>
    <div id="root"></div>
</body>
const content = [
        '<h2 class="home">Home</h2>',
        '<h3 class="hot">Hot Topics</h3>',
        ]
let navs = document.getElementsByTagName('nav');
        Array.from(navs).forEach((nav, index) => nav.addEventListener('click', () => {
            document.getElementById('root').innerHTML = content[index];
        }))

This creates a simple SPA where clicking navigation manually switches the content inside root, but the URL does not change; it only achieves "component switching."

The core functions of front-end routing are:

In other words, front-end routing establishes a mapping relationship between browser URL addresses and components.

2. Core Flow of Front-End Routing

  1. Modify the browser URL without refreshing the page.
  2. Listen for URL changes.
  3. Find the corresponding component from the mapping table based on the URL and render it.

We have two methods to implement this: hash routing and history routing.

3. Hash Principle

  1. What is a hash?

For example:

http://localhost:3000/#/home

Here, #/home is the hash. That is, the browser treats the string following the # in the URL as a hash value.

  1. Characteristics of hash
  1. Hash routing implementation steps
  1. Mapping table
const routes = [
    {
        path: '/home',
        component: () => {
            return '<h2>Home Page</h2>'
        }
    },
    {
        path: '/hot',
        component: () => {
            return '<h3>Hot Topics Page</h3>'
        }
    }
]
  1. Listening for hashchange
// Know that the URL has changed
window.addEventListener('hashchange', () => {
    // Find the corresponding path in routes and render the corresponding component
    renderView(location.hash)
})
  1. Render function
function renderView(hashVal) {
     hashVal = hashVal.replace('#', '')
     let route = routes.find(item => item.path === hashVal)
     if(route) {
         document.getElementById('root').innerHTML = route.component()
     }
 }
  1. Handling the initial load
// Initial page refresh
window.addEventListener('DOMContentLoaded', () => {
    renderView(location.hash)
})
  1. Hash routing summary Advantages:

Disadvantages:

4. History Routing Principle

  1. What is history?

History is an object provided by the browser for managing the browser's session history.

  1. Provided methods

pushState() can modify the URL without triggering a page refresh. Listening to popstate handles the browser's forward and backward navigation events.

5. Handwriting a History Router

  1. Define the route table
const routes = [
     {
         path: '/home',
         component: () => {
             return '<h2>History Home Page</h2>'
         }
     },
     {
         path: '/hot',
         component: () => {
             return '<h3>History Hot Topics Page</h3>'
         }
     }
 ]
  1. Prevent default navigation
e.preventDefault()
  1. Use pushState to modify the URL
history.pushState(null, '', el.getAttribute('href')) // Enter the browser's cache stack

This step achieves:

  1. Manually render the page
renderView(location.pathname)
  1. Listen for browser forward/backward navigation
window.addEventListener('popstate', () => {
    renderView(location.pathname)
})
  1. Render function
function onLoad() {
    // Path change, render corresponding component
    let linkList = document.querySelectorAll('a[href]')
    linkList.forEach(el => {
        el.addEventListener('click', (e) => {
            e.preventDefault() // Prevent default <a> tag navigation behavior
            history.pushState(null, '', el.getAttribute('href')) // Enter the browser's cache stack

            renderView(location.pathname)
        })
    })
}

function renderView(historyPath) {
    routes.forEach(item => {
        if (item.path === historyPath) {
            document.getElementById('root').innerHTML = item.component()
        }
    })
}

6. Summary

The essence of front-end routing is to switch page content based on browser URL changes without refreshing the page. It maintains a route mapping table to associate different paths with different components. When the URL changes, the front end listens for this change, finds the corresponding component, and renders it on the page.

There are two common implementation methods for front-end routing: hash mode and history mode. Hash mode implements route switching by listening for the hashchange event. It is characterized by simple implementation and good compatibility, but the URL contains a #. History mode modifies the URL using history.pushState and cooperates with popstate to listen for browser forward/backward navigation. The URL is cleaner, but the project requires server-side fallback configuration when going live; otherwise, refreshing the page may result in a 404 error.