skip to content

In Vue Router 5, how do createWebHistory, createWebHashHistory and createMemoryHistory differ, and when would you pick each one?

level: juniorimportance: must knowfreq 62%

answer

  1. three factories for one required option
  2. clean path vs text after #
  3. what the server receives on reload
  4. fragment never sent to the server
  5. memory: no address bar, SSR and tests

basics

~10 s

createWebHistory gives clean URLs but needs a server fallback to index.html; createWebHashHistory keeps the route after #, needing no server setup but hurting SEO; createMemoryHistory ignores the URL and suits SSR and tests.

solid answer

~40 s

The required `history` option of `createRouter()` takes one of three factories. `createWebHistory(base?)` is the recommended mode: URLs such as `/admin/users/7`, driven by the History API, but every reload or pasted link reaches the server, which must answer unknown app paths with `index.html` or the user gets a 404. `createWebHashHistory(base?)` puts the route after `#` (`/admin/#/users/7`); the fragment is never sent to the server, so it works on hosts you cannot configure and from `file://`, at the cost of uglier URLs and poor SEO. `createMemoryHistory(base?)` keeps its own in-memory stack and never touches the address bar, which is what server rendering and unit tests want. In Vue Router 5 these are the same factories v4 introduced to replace the old `mode` string.

code

ts · 12 lines
ts
import { createRouter, createWebHistory } from 'vue-router'
import UserList from '@/views/UserList.vue'

export const router = createRouter({
  // web history: needs the server fallback to index.html
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/', redirect: '/users' },
    { path: '/users', component: UserList },
    { path: '/users/:id', component: () => import('@/views/UserDetail.vue') },
  ],
})

go deeper

for a junior

Name the three factories and the URL each produces, and say why web history needs the server to return index.html on reload while hash history does not.

for a middle

Explain the fragment never reaching the server, the base argument each factory takes, and why memory history starts nowhere until a first navigation.

for a senior

Pick the mode from deployment constraints: who owns the server, SEO needs, SSR plans, and how you migrate old hash bookmarks without breaking links.

for a principal

Treat the history mode as a deployment contract between frontend and platform teams, and weigh the cost of a rewrite rule against URLs that live forever in bookmarks.

## What the `history` option decides A client-side router keeps two things in sync: the **current route location** (which records matched, which params) and a **URL**. In Vue Router 5, `createRouter({ history, routes })` takes a **history implementation**: an object that knows the starting location, how to write new entries and how to listen for back and forward. `history` is a required option; there is no default mode. The package exports three factories: - `createWebHistory(base?)`, often called **HTML5 mode** - `createWebHashHistory(base?)`, **hash mode** - `createMemoryHistory(base?)`, **memory mode** ## `createWebHistory`: clean URLs, the server must cooperate - URLs look like ordinary paths: `https://intranet.example/admin/users/7`. - In-app navigation writes history entries through the browser's History API, so the page never reloads. - The router docs call this the **recommended** mode. - The catch is the **first request**. A reload, a bookmark or a pasted link asks the server for `/admin/users/7`. A static server has no such file and answers 404. The fix is a **fallback**: any path that is not a real file returns the app's `index.html`, and the router then reads the path and renders the right view. On nginx that is a `try_files` rule ending in the app's `index.html`. - The fallback has a side effect: the server stops reporting 404 for unknown URLs, so the app needs its own catch-all route that renders a not-found view. ## `createWebHashHistory`: no server setup at all - The route lives after `#`: `https://intranet.example/admin/#/users/7`. - Browsers never send the fragment in an HTTP request, so the server only ever sees `/admin/` and serves `index.html`. No rewrite rules are needed. - The default base is `location.pathname + location.search`, and a `#` is appended when the base has none. Pages opened from `file://` have no host, and the router ignores the base there. - In development the router warns when a base contains `#` but does not end with `#` or `#/`. - The costs: URLs look worse, the docs warn about a **bad impact on SEO**, and the server can never tell which view a request was for. ## `createMemoryHistory`: no URL at all - It keeps its own stack of entries in memory and never reads or writes `window.location`. - It starts at a special empty location that points nowhere; the first real route comes from a `push` or `replace`. - It exists for **server-side rendering**, where there is no `window`, and it is the usual choice in **unit tests**. - In a browser app it works, but the address bar and the browser's back and forward buttons are disconnected from the app; `router.back()` only moves within the in-memory stack. ## What each mode reads at startup The difference is easiest to see in how each history computes the **starting location** the router navigates to: - **Web history** takes `location.pathname`, strips the base from its front, and appends `location.search` and `location.hash`. A visit to `/admin/users/7?tab=roles` with base `/admin/` starts at `/users/7?tab=roles`. - **Hash history** ignores the path and reads the text after the base's `#`, adding a leading `/` if it is missing. A visit to `/admin/#/users/7` starts at `/users/7`. - **Memory history** reads nothing from the browser; its first entry is empty. That is also why switching modes changes what old links mean: a hash-style link opened under web history arrives as a route hash on the root path, not as a route. ## Side by side | | `createWebHistory` | `createWebHashHistory` | `createMemoryHistory` | |---|---|---|---| | URL shape | `/admin/users/7` | `/admin/#/users/7` | unchanged | | Server config | fallback to `index.html` required | none | not applicable | | SEO | good | poor | not applicable | | Browser buttons | work | work | disconnected | | Typical home | production SPA | locked-down static host, `file://` | SSR, unit tests | ## Choosing, and what changed since Vue Router 3 1. Default to **web history** whenever you control the server or its config, as with an internal admin app behind your own nginx. 2. Fall back to **hash history** when you cannot add a rewrite rule, or when the app is opened from disk. 3. Use **memory history** for the server side of SSR and in tests, never as a shortcut to avoid configuring a server. Vue Router 3 picked the mode with a string, `mode: 'history' | 'hash' | 'abstract'`, plus a separate `base` option. Vue Router 4 replaced both with the factories above (`abstract` became `createMemoryHistory`) and moved `base` into the factory's first argument. The stated reasons were **tree shaking** (an app ships only the history it uses) and the ability to plug in **custom histories**. Vue Router 5 kept this API unchanged, so v4 code and tutorials still apply here.

  • After the nginx fallback goes in, a mistyped admin URL returns 200 with the app shell. How do you still show a not-found page?
    The server now answers every unknown path with `index.html`, so it cannot report 404 any more. Add a last catch-all route in the router, the `/:pathMatch(.*)*` pattern, that renders a not-found view. The HTTP status stays 200 for a pure SPA; only a server that matches routes itself, as in SSR, can send a real 404.
  • Why did Vue Router 4 turn the `mode` string into history factory functions?
    A string forced the router to bundle every history implementation, because any mode could be chosen at runtime. Importing a factory lets the bundler drop the histories you never use, and it lets you pass your own object that implements the router's history interface. `base` moved into the factory's argument at the same time.
  • Your admin app switches from hash to web history. What happens to an old bookmark like `/admin/#/users/7`?
    Web history reads the path, `/admin/`, and treats `#/users/7` as the route's hash, so the user lands on the home route. Handle it once at startup: before creating the router, or in a guard on `/`, turn a fragment that starts with `#/` into a real path with a replace, so old links keep working.

saying these in an interview costs you the question

  • Hash mode still needs rewrites because the part after # reaches the server.
  • createWebHistory works on any static host with no server configuration.
  • createMemoryHistory is a fine production choice for a normal browser SPA.
  • You choose the mode with mode: 'history' in createRouter().
  • Hash URLs are indexed as well as clean paths, so SEO is the same.