- 前端
- 路由
【免费下载链接】vue-router
🚦 The official router for Vue 2
导读
本文基于 vue-router 官方文档 docs-gitbook/fr/essentials/history-mode.md(法语版,英文对照见 docs/guide/essentials/history-mode.md),系统讲解 vue-router 的history 模式(HTML5 History Mode):它是什么、与默认 hash 模式的本质区别、如何启用、为什么必须配置服务器 fallback、如何在 Apache/nginx/Node.js/Express/IIS/Caddy/Firebase Hosting 等主流服务器上落地配置,以及如何在前端应用内兜底 404。读完本文,你将掌握 history 模式从原理到部署的全链路实战方案,并能依据仓库源码解释其内部行为。
一、从 hash 模式到 history 模式:为什么要去"#"?
1.1 默认的 hash 模式及其工作原理
vue-router的默认模式是hash 模式。它利用 URL 的 hash(#之后的部分)来模拟一个完整的 URL,从而在 URL 变化时不触发页面重新加载。
在 src/history/hash.js 中可以看到 hash 模式的实现细节:它通过getHash()读取window.location.href中#之后的内容作为路由路径(注意源码注释特别说明:不能直接使用window.location.hash,因为 Firefox 会提前对其做 URL 解码,导致各浏览器行为不一致),并通过pushHash/replaceHash更新 hash 部分完成导航。
hash 模式 URL 形如http://oursite.com/#/user/id。hash 部分不会作为 HTTP 请求发送给服务器,因此服务器天然无需任何额外配置,这就是 hash 模式"开箱即用"的原因。
1.2 history 模式:基于history.pushState的"干净"URL
为了去掉 URL 中的#,可以使用history 模式,它借助 HTML5 的history.pushStateAPI 实现无刷新的 URL 导航。启用方式只需在创建路由时指定mode: 'history':
const router = new VueRouter({ mode: 'history', routes: [...] })启用后,URL 将和普通网站一样,例如http://oursite.com/user/id。
从源码看,history 模式由 src/history/html5.js 中的HTML5History类实现。其核心行为包括:
push()在导航完成后调用pushState(cleanPath(this.base + route.fullPath))更新地址栏(对应 src/util/push-state.js 中的pushState,内部调用window.history.pushState,并对 Safari 的 "DOM Exception 18"(限制 100 次 pushState 调用)做了 try/catch 兜底回退到window.location.assign);replace()对应replaceState;setupListeners()监听window上的popstate事件——当用户点击浏览器前进/后退按钮时,通过popstate回调触发transitionTo完成无刷新路由切换;getLocation()负责从window.location.pathname解析出当前路径,并正确处理base前缀与查询参数、hash。
1.3 关键区别对比
| 维度 | hash 模式(默认) | history 模式 |
|---|---|---|
| URL 形态 | http://oursite.com/#/user/id | http://oursite.com/user/id |
| 底层 API | 修改 location.hash | history.pushState/replaceState |
| 服务器配置 | 不需要 | 必须配置 fallback(见下文) |
| 兼容性 | 支持所有 Vue 支持浏览器,包括不支持 HTML5 History API 的旧浏览器 | 需要支持 HTML5 History API 的现代浏览器 |
| 监听事件 | popstate(支持 pushState 时)或hashchange(见 src/history/hash.js) | popstate |
注意:上述"不需要/必须"的结论有前提——hash 模式下 hash 变化不会向服务器发起新请求,所以刷新、直接访问都不会 404;而 history 模式的路由路径会真实地发到服务器,直接访问
/user/id时服务器若不知道这个路径,就会返回 404。
二、核心问题:为什么 history 模式会 404,以及解决思路
2.1 问题根源
history 模式下的应用本质是单页应用(SPA):路由切换完全由前端 JS 控制,页面上只有一份index.html。问题在于:
当用户在地址栏直接输入http://oursite.com/user/id(或刷新该页面)时,浏览器会向服务器发起一个针对/user/id的真实 HTTP 请求。而服务器上并不存在/user/id这个静态资源(只有/index.html),于是返回404 错误页。
2.2 解决思路:服务器 catch-all fallback
解决办法并不复杂:为服务器添加一条"兜底"规则——当请求的 URL 不匹配任何静态文件(且不是目录)时,一律返回应用入口页index.html。这样,无论用户访问什么深链接(deep link),浏览器都会先拿到index.html,随后由前端路由接管并渲染出对应页面。而真正的静态资源(JS/CSS/图片等)仍然按原路径正常返回。
该策略的前提是:应用部署在服务器根目录。如果你的应用部署在子目录下,则需要:
- 使用构建工具(如 Vue CLI)的
publicPath配置资源路径; - 配合路由的
base选项(下文详细介绍); - 把下面各服务器的示例配置中的根目录路径改为你的子目录路径(例如把 Apache 的
RewriteBase /改为RewriteBase /name-of-your-subfolder/)。
三、六种主流服务器的 fallback 配置实例
以下配置均假设应用部署在服务器根目录。
3.1 Apache
使用mod_rewrite模块,将不存在的文件/目录请求重写回index.html:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule>逐行解读:
RewriteEngine On:启用重写引擎;RewriteBase /:设置重写基准路径为站点根目录;RewriteRule ^index\.html$ - [L]:对已经请求index.html本身的 URL 直接放行(-表示不重写,[L]表示这是最后一条规则);RewriteCond %{REQUEST_FILENAME} !-f:请求的路径不是一个真实存在的文件;RewriteCond %{REQUEST_FILENAME} !-d:请求的路径不是一个真实存在的目录;RewriteRule . /index.html [L]:上述条件都满足时,把任意请求重写到/index.html。
另一种方案:不用mod_rewrite,可以改用 Apache 的FallbackResource指令,效果类似但配置更简洁。
3.2 nginx
在location /中使用try_files指令:
location / { try_files $uri $uri/ /index.html; }含义:依次尝试"按原样提供文件"→"按目录提供"→"都不行就回退到/index.html"。
3.3 Node.js 原生(不使用框架)
一个最小可用的原生 Node.js 服务器示例:
const http = require('http') const fs = require('fs') const httpPort = 80 http.createServer((req, res) => { fs.readFile('index.html', 'utf-8', (err, content) => { if (err) { console.log('We cannot open "index.html" file.') } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) res.end(content) }) }).listen(httpPort, () => { console.log('Server listening on: http://localhost:%s', httpPort) })这段代码对所有请求都返回index.html内容。注意:它只演示了"回退"的核心逻辑,没有区分静态资源;实际项目中应先用静态资源中间件/路由匹配真实文件,未命中再回退到index.html。
3.4 Node.js + Express
对于 Express 应用,官方文档推荐使用connect-history-api-fallback 中间件,它专门处理 SPA 的 history 模式回退逻辑(区分真实请求、Accept头、带.的文件等场景),用法如下:
const express = require('express') const history = require('connect-history-api-fallback') const serveStatic = require('serve-static') const app = express() app.use(history()) app.use(serveStatic(__dirname + '/dist')) app.listen(3000)仓库佐证:本仓库的 examples/server.js 使用 Express +
express-urlrewrite把/<exampleName>/*重写为对应示例目录的index.html,正是"把深链接回退到入口页"这一思路的实践样板。
3.5 IIS(Internet Information Services)
- 安装 IIS UrlRewrite 模块;
- 在站点根目录创建
web.config,内容如下:
<?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>规则解读:匹配所有 URL,当请求不是文件(IsFile取反)且不是目录(IsDirectory取反)时,重写为根路径/(即返回首页index.html)。
3.6 Caddy 与 Firebase Hosting
Caddy v2(新版本使用内置try_files指令):
try_files {path} /Caddy v1(旧版本使用 rewrite 块):
rewrite { regexp .* to {path} / }Firebase Hosting:在firebase.json中添加rewrites规则,将所有请求重写到/index.html:
{ "hosting": { "public": "dist", "rewrites": [ { "source": "**", "destination": "/index.html" } ] } }四、mode 与 base 的源码级解析
4.1mode选项的取值与默认值
根据 docs/api/README.md 的官方 API 文档,mode选项定义如下:
- type:
string - 默认值:
"hash"(浏览器环境) /"abstract"(Node.js 环境) - 可选值:
"hash" | "history" | "abstract"hash:使用 URL hash 路由,兼容所有 Vue 支持的浏览器,包括不支持 HTML5 History API 的旧浏览器;history:需要 HTML5 History API 且必须配合服务器配置;abstract:适用于所有 JavaScript 环境(如 Node.js 服务端渲染)。当浏览器 API 不存在时,路由会被自动强制切换到该模式。
在 src/router.js 中可以看到模式的实际决策逻辑:
let mode = options.mode || 'hash' this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' }即:显式传入mode: 'history',但浏览器不支持history.pushState(supportsPushState为 false)且未把fallback设为false时,路由会自动回退到 hash 模式;在非浏览器环境(如 Node.js)下则强制使用 abstract 模式。
supportsPushState的定义见 src/util/push-state.js:它检测window.history.pushState是否存在,并额外排除了 Android 2.x / Android 4.0 的旧版 Mobile Safari 内核浏览器(这类浏览器虽实现了 API 但行为有缺陷)。
4.2base选项:子目录部署的关键
当整个 SPA 被部署在子路径(例如/app/)下时,应通过base选项告知路由:
const router = new VueRouter({ mode: 'history', base: '/app/', routes: [...] })官方文档对base的说明(docs/api/README.md):type 为string,默认值为"/",表示应用的基准 URL。例如整个应用挂在/app/下,base就应设为"/app/"。
从源码看,base在 src/history/base.js 的normalizeBase中被规范化:优先读取页面<base>标签的href(剥离掉协议和域名部分),确保以/开头并去掉尾部/。随后在 src/history/html5.js 的getLocation中,会按base前缀剥离出真正的路由路径(源码注释专门提到base="/a"时不能把/app误判为/a/pp这类边界 case,因此会在匹配时补上尾斜杠)。在 src/router.js 的createHref中,生成链接时也会把base拼回 URL。文档还提示:history 模式下使用base后,<router-link>的to属性无需再包含base前缀。
4.3fallback选项
fallback(type:boolean,默认true,见 docs/api/README.md)控制当浏览器不支持history.pushState但配置了mode: 'history'时,是否回退到 hash 模式。若设为false,在 IE9 等旧浏览器中每次<router-link>导航都会变成整页刷新;这一设置在服务端渲染(SSR)且需兼容 IE9的场景下有用,因为 hash 模式的 URL 不适用于 SSR。
仓库中的 examples/basic/app.js 与 examples/hash-mode/app.js 分别演示了mode: 'history'与mode: 'hash'的完整用法,可作为对比参考。
五、历史模式下的 404 兜底:前端 catch-all 路由
5.1 副作用:服务器不再报告 404
配置了服务器的 catch-all 回退后,会出现一个新的副作用:服务器对任何不存在的路径都会返回index.html,HTTP 404 状态码不再出现。因为所有"未命中"的请求都被回退到了应用入口页。
5.2 解法:在 Vue 应用内实现 catch-all 路由
正确的做法是在 Vue 应用内部添加一条匹配所有路径的兜底路由,用来渲染 404 页面:
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ] })其中path: '*'是 vue-router 中经典的通配符(wildcard)路径,用于匹配所有未匹配到的路径。仓库的 test/unit/specs/create-map.spec.js 中就有{ path: '*', name: 'wildcard', component: Baz }这样的测试用例来验证通配符匹配行为。
版本提示:本项目为 Vue 2 的 vue-router(
*语法对应path-to-regexp旧版通配符写法)。在 Vue Router 4 中,通配符语法已改为具名参数正则形式{ path: '/:pathMatch(.*)*', ... }。本文以当前仓库(Vue 2)的实现为准。
需要注意:通配符路由通常会放在路由表最后,避免它提前拦截掉其他正常路由的匹配。
5.3 进阶方案:Node.js 服务端路由做 404
如果你的服务器是 Node.js,还可以在服务端实现同样的回退逻辑:用服务端路由匹配每个进来的 URL,如果匹配不到任何路由,就明确返回 404 响应。这种方案的优势在于:
- 深链接首次访问就能得到正确的 404 状态码,利于 SEO;
- 未匹配路径不会白白返回
index.html再让前端二次判断。
这一做法正是**服务端渲染(SSR)**的标准路径,可参阅 Vue 官方 SSR 文档了解详情。
六、为什么"无刷新"?——源码层面的行为链路
为了深入理解 history 模式的原理,这里把"点击链接 → URL 变化 → 视图更新"的完整链路串起来:
- 拦截点击:
<router-link>组件拦截默认跳转行为,改为调用router.push()(组件实现在 src/components/link.js)。官方 API 文档(docs/api/README.md)也说明:history 模式下router-link会拦截点击事件,避免浏览器整页刷新。 - 执行导航:
router.push()委托给 history 实例的push()(src/history/html5.js),先执行transitionTo完成路由匹配与守卫(guard)流程(见 src/history/base.js 的transitionTo与confirmTransition)。 - 更新地址栏:导航确认后调用
pushState(cleanPath(this.base + route.fullPath)),通过window.history.pushState把新 URL 写入地址栏——该操作不会触发页面刷新,这正是"无刷新导航"的关键。 - 更新视图:
updateRoute把新 route 写入this.current并通知 Vue 响应式更新,<router-view>渲染对应组件。 - 响应浏览器前进/后退:用户点击前进/后退按钮时,浏览器触发
popstate事件(不刷新页面),HTML5History.setupListeners中注册的回调捕获到该事件后调用transitionTo完成反向导航(src/history/html5.js)。
由此可以得出一个重要结论:history 模式下的 URL 完全由前端 JS 维护,服务器端并不存在/user/id这样的真实文件。这正是"直接访问深链接必须依赖服务器 fallback"的根本原因,也是本文所有服务器配置要解决的唯一问题。
七、常见问题与部署清单
7.1 常见问题速查
| 现象 | 原因 | 解决方案 |
|---|---|---|
直接访问/user/id返回 404 | 服务器未配置 fallback | 按第三节为你的服务器添加 catch-all 重写规则 |
| 刷新后 404,但站内点击导航正常 | 同上 | 同上 |
| 部署在子目录后链接错乱 | 未配置base | 设置base: '/app/'并调整服务器根路径规则 |
旧浏览器下 URL 出现# | 浏览器不支持 pushState,触发了自动 fallback | 属正常降级;若需 SSR 场景可设fallback: false |
| 不存在的路径显示了首页而非 404 页 | 服务器不再返回 404 | 前端添加path: '*'通配符路由渲染 404 组件 |
7.2 上线前检查清单
- 路由创建时是否设置了
mode: 'history'; - 服务器是否已配置对应平台(Apache/nginx/Node/Express/IIS/Caddy/Firebase)的 fallback 规则;
- 若部署在子目录,
base与服务器重写基准是否一致; - 是否在路由表末尾添加了通配符 404 兜底路由;
- 静态资源是否仍能正常返回(fallback 规则应只拦截"非文件、非目录"的请求)。
参考文档与源码索引
- 本文主体文档:docs-gitbook/fr/essentials/history-mode.md(英文版 docs/guide/essentials/history-mode.md)
- mode / base / fallback 选项说明:docs/api/README.md
- history 模式实现:src/history/html5.js
- hash 模式实现:src/history/base.js(hash 实现见 src/history/hash.js)
- pushState 能力检测与调用:src/util/push-state.js
- 模式选择逻辑:src/router.js
- 通配符路由测试:test/unit/specs/create-map.spec.js
- 示例:history 模式 examples/basic/app.js、hash 模式 examples/hash-mode/app.js
- 前端
- 路由
【免费下载链接】vue-router
🚦 The official router for Vue 2
相关推荐
Vue Router 2 的 HTML5 History 模式:从配置到服务器回退的完整实战指南
Vue Router 2 的 HTML5 History 模式:从配置到服务器回退的完整实战指南 导读 vue router 默认使用 Hash 模式(URL
前端路由electerm 使用指南:SSH、桌面与文件传输,一个窗口全搞定
electerm 使用指南:SSH、桌面与文件传输,一个窗口全搞定 这篇 electerm 使用教程以"先跑起来"的思路展开:讲清安装、首次配置和连上第一台 S
前端路由vue-router HTML5 History 模式实战指南:原理、服务端配置与 404 兜底方案
vue router HTML5 History 模式实战指南:原理、服务端配置与 404 兜底方案 vue router 默认使用 hash 模式,通过 UR
前端路由
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考