skip to content

Setup & History Modes

Creating the router, choosing web, hash or memory history, and awaiting isReady() before mount. Interviewers check the server fallback HTML5 mode needs and the v3 to v4 API breaks.

on this pageshow

explore

questions

5

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.
open as a page

In Vue Router 5, what does app.use(router) start, and why would you await router.isReady() before calling app.mount()?

level: middleimportance: should knowfreq 50%

basics

~20 s

app.use(router) registers RouterLink and RouterView, exposes $router and $route, and in the browser starts the asynchronous first navigation; awaiting isReady() mounts only after it finishes, avoiding an empty first render and a spurious enter transition.

open as a page

In Vue Router 5, why do unit tests and SSR server entries use createMemoryHistory, and what must happen before the first render?

level: middleimportance: should knowfreq 33%

basics

~10 s

createMemoryHistory keeps locations in memory, so the server needs no window and tests share no URL; push the starting route and await router.isReady() before rendering, because on the server nothing starts the first navigation.

open as a page

In Vue Router 5, a Vite-built admin SPA is served by nginx under /admin/; how do you set the router base, route paths and fallback?

level: middleimportance: should knowfreq 44%

basics

~10 s

Set Vite's base to /admin/, pass import.meta.env.BASE_URL to createWebHistory(), keep route paths app-relative like /users, and make nginx fall back to /admin/index.html for unknown paths under /admin/.

open as a page

Upgrading a Vue 2 admin app from Vue Router 3 to Vue Router 5, which router setup code breaks, and what does Vue Router 5 itself change?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Vue Router 4 broke the setup: new VueRouter becomes createRouter, mode and base become a history factory and its argument, onReady becomes isReady, and currentRoute becomes a ref; Vue Router 5 adds no breaks for a plain v4 app.

open as a page