Different History Modes
Configure how Vue Router handles browser history and URL management for optimal user experience and SEO.
Overview
Vue Router provides different history modes to handle navigation and URL management. The choice of history mode affects how URLs appear in the browser and how your application handles routing.
HTML5 History Mode
Basic Setup
The HTML5 mode uses the browser's History API and is the recommended approach for most applications:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
]
})Custom Base URL
If your app is deployed to a subdirectory, specify the base URL:
const router = createRouter({
history: createWebHistory('/my-app/'),
routes: [
// routes...
]
})Advantages
- Clean URLs: URLs look natural (e.g.,
https://example.com/user/123) - SEO Friendly: Search engines can properly index your pages
- Better UX: Users can share and bookmark meaningful URLs
- Server-side Rendering: Compatible with SSR applications
Server Configuration Required
HTML5 mode requires server configuration to handle client-side routing. Without proper setup, users will get 404 errors when accessing URLs directly.
Hash Mode
Basic Setup
Hash mode uses URL fragments and doesn't require server configuration:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
]
})Custom Base URL
const router = createRouter({
history: createWebHashHistory('/my-app/'),
routes: [
// routes...
]
})Characteristics
- URLs with Hash: URLs include a hash symbol (e.g.,
https://example.com/#/user/123) - No Server Config: Works without server-side configuration
- SEO Limitations: Hash fragments aren't sent to servers, affecting SEO
- Legacy Support: Works with older browsers
When to Use Hash Mode
- Static hosting without server configuration control
- Legacy browser support requirements
- Quick prototyping and development
- Electron or mobile app development
Memory Mode
Basic Setup
Memory mode doesn't interact with the browser URL and is ideal for server-side environments:
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
// For SSR, manually trigger initial navigation
router.push('/initial-route')Use Cases
- Server-Side Rendering (SSR): Node.js environments
- Testing: Unit and integration tests
- Native Apps: Electron, Capacitor, or Cordova applications
- Embedded Contexts: When URL management isn't needed
Limitations
- No browser history navigation (back/forward buttons)
- URLs don't change in the browser
- Not suitable for typical web applications
Server Configuration Examples
Apache
Create or update .htaccess file:
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>Alternative using FallbackResource:
FallbackResource /index.htmlNginx
Add to your server configuration:
location / {
try_files $uri $uri/ /index.html;
}For subdirectory deployment:
location /my-app/ {
try_files $uri $uri/ /my-app/index.html;
}Node.js (Express)
Using connect-history-api-fallback middleware:
const express = require('express')
const history = require('connect-history-api-fallback')
const path = require('path')
const app = express()
// Apply the fallback middleware before your static middleware
app.use(history({
// Exclude API routes
rewrites: [
{ from: /^\/api\/.*$/, to: function(context) {
return context.parsedUrl.pathname;
}}
]
}))
// Serve static files
app.use(express.static(path.join(__dirname, 'dist')))
app.listen(3000)Native Node.js
Basic implementation without frameworks:
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url)
const pathname = parsedUrl.pathname
// Check if file exists
const filePath = path.join(__dirname, 'dist', pathname)
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
// File doesn't exist, serve index.html
fs.readFile(path.join(__dirname, 'dist', 'index.html'), 'utf-8', (err, content) => {
if (err) {
res.writeHead(500)
res.end('Server Error')
return
}
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(content)
})
} else {
// Serve the requested file
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(500)
res.end('Server Error')
return
}
res.writeHead(200)
res.end(content)
})
}
})
})
server.listen(3000, () => {
console.log('Server running on http://localhost:3000')
})IIS (Internet Information Services)
Create web.config in your root directory:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Handle History Mode and custom 404/500" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>Caddy
Caddy v2:
try_files {path} /index.htmlCaddy v1:
rewrite {
regexp .*
to {path} /
}Firebase Hosting
Add to firebase.json:
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}Netlify
Create _redirects file in your public directory:
/* /index.html 200Or use netlify.toml:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Vercel
Create vercel.json:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}AWS S3 + CloudFront
Configure CloudFront error pages:
- Go to CloudFront distribution settings
- Add custom error page for 404 errors
- Set response page path to
/index.html - Set HTTP response code to 200
Handling 404 Errors
Client-Side 404 Page
Since all routes now serve index.html, implement a catch-all route:
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User },
// Catch-all route - must be last
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
})404 Component Example
<template>
<div class="not-found">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<router-link to="/">Go Home</router-link>
</div>
</template>
<script>
export default {
name: 'NotFound'
}
</script>Server-Side 404 Handling
For Node.js applications, you can implement server-side route matching:
const express = require('express')
const { createRouter, createMemoryHistory } = require('vue-router')
const routes = require('./routes') // Your route definitions
const app = express()
app.get('*', (req, res) => {
const router = createRouter({
history: createMemoryHistory(),
routes
})
router.push(req.url)
router.isReady().then(() => {
const matchedRoutes = router.currentRoute.value.matched
if (matchedRoutes.length === 0) {
res.status(404).send('Page not found')
} else {
// Serve your Vue app
res.sendFile(path.join(__dirname, 'dist', 'index.html'))
}
})
})Best Practices
1. Choose the Right Mode
// ✅ Production web app with server control
const router = createRouter({
history: createWebHistory(),
routes
})
// ✅ Static hosting or legacy support
const router = createRouter({
history: createWebHashHistory(),
routes
})
// ✅ SSR or testing
const router = createRouter({
history: createMemoryHistory(),
routes
})2. Environment-Based Configuration
const router = createRouter({
history: process.env.NODE_ENV === 'production'
? createWebHistory('/my-app/')
: createWebHistory(),
routes
})3. Graceful Fallbacks
// Check for History API support
const supportsHistory = 'pushState' in window.history
const router = createRouter({
history: supportsHistory
? createWebHistory()
: createWebHashHistory(),
routes
})4. Base URL Configuration
// Use environment variables for base URL
const router = createRouter({
history: createWebHistory(process.env.VUE_APP_BASE_URL || '/'),
routes
})Common Issues and Solutions
1. 404 on Direct Access
Problem: Users get 404 when accessing URLs directly
Solution: Configure server fallback to index.html
2. Assets Not Loading
Problem: CSS/JS assets return 404 in subdirectory deployments
Solution: Configure correct base URL and asset paths
// vite.config.js
export default {
base: '/my-app/',
// ...
}
// Vue Router
const router = createRouter({
history: createWebHistory('/my-app/'),
routes
})3. API Routes Conflict
Problem: API routes are caught by history fallback
Solution: Exclude API routes from fallback
// Express with connect-history-api-fallback
app.use(history({
rewrites: [
{ from: /^\/api\/.*$/, to: function(context) {
return context.parsedUrl.pathname;
}}
]
}))4. Hash Mode SEO Issues
Problem: Search engines can't index hash routes
Solution: Use HTML5 mode with proper server configuration, or implement prerendering
Testing Different Modes
Unit Testing
import { createRouter, createMemoryHistory } from 'vue-router'
import { mount } from '@vue/test-utils'
describe('Router', () => {
it('navigates correctly', async () => {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
await router.push('/about')
expect(router.currentRoute.value.path).toBe('/about')
})
})E2E Testing
// Cypress example
describe('Navigation', () => {
it('handles direct URL access', () => {
cy.visit('/user/123')
cy.contains('User Profile')
cy.url().should('include', '/user/123')
})
it('maintains URL on refresh', () => {
cy.visit('/about')
cy.reload()
cy.url().should('include', '/about')
})
})Migration Guide
From Hash to HTML5 Mode
- Update router configuration:
// Before
const router = createRouter({
history: createWebHashHistory(),
routes
})
// After
const router = createRouter({
history: createWebHistory(),
routes
})- Configure server fallback
- Update any hardcoded hash URLs
- Test all routes work with direct access
From Vue 2 to Vue 3
// Vue 2
const router = new VueRouter({
mode: 'history',
routes
})
// Vue 3
const router = createRouter({
history: createWebHistory(),
routes
})Next Steps
- Learn about Route Matching Syntax for advanced patterns
- Explore Navigation Guards for route protection
- Understand Lazy Loading Routes for performance optimization