Frontend Development 20 min read

Deep Dive into Vue Router 4: createWebHistory, Web History API, and Source Code Analysis

This article explains how Vue Router 4 leverages the HTML5 Web History API through createWebHistory, detailing pushState/replaceState mechanics, server fallback handling, TypeScript definitions, the four‑step creation process, listener implementation, and comparisons with hash and memory histories, all illustrated with real source code.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Deep Dive into Vue Router 4: createWebHistory, Web History API, and Source Code Analysis

The article begins by introducing the series on Vue Router 4 source code and states that it will explore the part of the code dealing with the Web History API, which underlies the official history mode of Vue Router.

Purpose

Readers will learn how Vue Router uses the Web History API, and understand the implementation details of createWebHistory and createWebHashHistory .

Web History API Basics

The H5 History API provides two key functions, pushState() and replaceState() , which change the URL without a server round‑trip. The length property reflects the number of entries in the session history stack, and scrollRestoration controls automatic or manual scroll position restoration.

When pushState is called, the browser URL changes but the page content stays the same, and the history length increments.

Server Adaptation

Using pushState or replaceState can cause a 404 on page refresh because the server does not recognize the client‑side path. The solution is to add a fallback route that serves index.html for any unmatched URL.

createWebHistory TypeScript Definition

export declare function createWebHistory(base?: string): RouterHistory
/**
 * Interface implemented by History implementations that can be passed to the router as Router.history
 */
export interface RouterHistory {
  readonly base: string
  readonly location: HistoryLocation
  readonly state: HistoryState
  push(to: HistoryLocation, data?: HistoryState): void
  replace(to: HistoryLocation, data?: HistoryState): void
  go(delta: number, triggerListeners?: boolean): void
  listen(callback: NavigationCallback): () => void
  createHref(location: HistoryLocation): string
  destroy(): void
}

The article notes that createRouter creates a Vue Router instance whose history object is built by createWebHistory , exposing methods like push , replace , and listeners.

Implementation Process (Four Steps)

Create the router history object with properties location , state , push , and replace .

Create router listeners that handle state changes and custom navigation callbacks.

Add a location proxy so that accessing routerHistory.location returns a normalized path.

Add a state proxy so that accessing routerHistory.state returns the current history state.

The core source for step 1 is:

export function createWebHistory(base?: string): RouterHistory {
  base = normalizeBase(base)
  // Step 1: create history object
  const historyNavigation = useHistoryStateNavigation(base)
  // Step 2: create listeners
  const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace)
  function go(delta: number, triggerListeners = true) {
    if (!triggerListeners) historyListeners.pauseListeners()
    history.go(delta)
  }
  const routerHistory: RouterHistory = assign({
    location: '',
    base,
    go,
    createHref: createHref.bind(null, base),
  }, historyNavigation, historyListeners)
  // Step 3: location proxy
  Object.defineProperty(routerHistory, 'location', {
    enumerable: true,
    get: () => historyNavigation.location.value,
  })
  // Step 4: state proxy
  Object.defineProperty(routerHistory, 'state', {
    enumerable: true,
    get: () => historyNavigation.state.value,
  })
  return routerHistory
}

push and replace implementations

Both methods ultimately call a shared changeLocation function that builds the final URL, decides between pushState and replaceState , and updates the history stack.

function changeLocation(to: HistoryLocation, state: StateEntry, replace: boolean): void {
  const hashIndex = base.indexOf('#')
  const url = hashIndex > -1
    ? (location.host && document.querySelector('base') ? base : base.slice(hashIndex)) + to
    : createBaseLocation() + base + to
  try {
    history[replace ? 'replaceState' : 'pushState'](state, '', url)
    historyState.value = state
  } catch (err) {
    if (__DEV__) {
      warn('Error with push/replace State', err)
    } else {
      console.error(err)
    }
    location[replace ? 'replace' : 'assign'](url)
  }
}

The push method records the current scroll position, inserts a temporary entry to preserve it, then performs the final navigation; replace directly updates the state and URL.

Router Listeners

Listeners react to popstate , beforeunload , and expose three hook functions:

pauseListeners – temporarily stop listening.

listen – register a navigation callback and receive a teardown function.

destroy – remove all listeners and clean up event handlers.

const popStateHandler: PopStateListener = ({ state }) => {
  const to = createCurrentLocation(base, location)
  const from = currentLocation.value
  const fromState = historyState.value
  let delta = 0
  if (state) {
    currentLocation.value = to
    historyState.value = state
    if (pauseState && pauseState === from) {
      pauseState = null
      return
    }
    delta = fromState ? state.position - fromState.position : 0
  } else {
    replace(to)
  }
  listeners.forEach(listener => {
    listener(currentLocation.value, from, {
      delta,
      type: NavigationType.pop,
      direction: delta ? (delta > 0 ? NavigationDirection.forward : NavigationDirection.back) : NavigationDirection.unknown,
    })
  })
}

function beforeUnloadListener() {
  const { history } = window
  if (!history.state) return
  history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '')
}

Hash and Memory Histories

The article also briefly covers createWebHashHistory (which uses the URL hash) and createMemoryHistory (used for SSR). The memory history keeps a queue of locations and a position index, providing push , replace , go , and listener management without relying on the browser's History API.

export function createMemoryHistory(base: string = ''): RouterHistory {
  let listeners: NavigationCallback[] = []
  let queue: HistoryLocation[] = [START]
  let position = 0
  base = normalizeBase(base)
  function setLocation(location: HistoryLocation) {
    position++
    if (position === queue.length) {
      queue.push(location)
    } else {
      queue.splice(position)
      queue.push(location)
    }
  }
  function triggerListeners(to, from, { direction, delta }) {
    const info = { direction, delta, type: NavigationType.pop }
    listeners.forEach(cb => cb(to, from, info))
  }
  const routerHistory: RouterHistory = {
    location: START,
    state: {},
    base,
    createHref: createHref.bind(null, base),
    replace(to) {
      queue.splice(position--, 1)
      setLocation(to)
    },
    push(to, data) {
      setLocation(to)
    },
    listen(cb) {
      listeners.push(cb)
      return () => {
        const i = listeners.indexOf(cb)
        if (i > -1) listeners.splice(i, 1)
      }
    },
    destroy() {
      listeners = []
      queue = [START]
      position = 0
    },
    go(delta, shouldTrigger = true) {
      const from = this.location
      const direction = delta < 0 ? NavigationDirection.back : NavigationDirection.forward
      position = Math.max(0, Math.min(position + delta, queue.length - 1))
      if (shouldTrigger) triggerListeners(this.location, from, { direction, delta })
    },
  }
  Object.defineProperty(routerHistory, 'location', { enumerable: true, get: () => queue[position] })
  return routerHistory
}

Conclusion

The article wraps up by summarizing the benefits of the Web History API—URL changes without server interaction, state passing between routes, and broad browser compatibility—showing how Vue Router encapsulates these capabilities in its history implementations.

JavaScriptfrontend developmentVue RoutercreateWebHistoryWeb History API
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.