一、什么是hash/history模式
对于vue这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,即Vue-Router 。前端路由的核心在于改变视图的同时不会向后端发出请求。
为了实现改变视图不会向后端发送请求,浏览器提供了hash及history两种模式。
- hash模式:url中会有#存在如www.baidu.com/#/a,看起来丑陋且不好做SEO,但是由于路由器向服务器发请求的时候会忽略#后面的值,所以在浏览器刷新的时候都是很正常的不会再次发请求。
- history模式:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。但当没有后端进行相应配置时,url就是我们看的正常的路由样子,就是因为看起来很像真的,所以刷新浏览器的时候(浏览器向服务器请求)会发现服务器根本没有这个路径资源,所以返回404。
二、history模式导致的问题
1、刷新浏览器返回404
// routes.js
export default [
{
path: '/',
redirect: '/app'
},
{
path: '/app',
component: Todo
},
{
path: '/login',
component: Login
}
打开浏览器访问http://localhost:8000/,会自动跳转到http://localhost:8000/app
当直接输入http://localhost:8000/login或者http://localhost:8000/app 会返回404
原因:
- history模式——浏览器向服务器发送了http://localhost:8000/app或者http://localhost:8000/login请求,服务器找不到/app或者/login资源,所以返回404。
- hash模式下浏览器只会发送http://localhost:8000/请求,并由前端路由处理/app和/login
解决方法:用webpack对devServer进行配置
// 当vue-router采用history模式时,必须有后端进行配置,否则当页面刷新时会出现404,
// 这是因为后端不认识当前刷新的路由,找不到对应的路径,所以会返回404,
// 在开发环境下为了解决这个问题可以进行historyApiFallback配置使得任意的 404 响应都可能需要被替代为 index.html。
// 若在webpack的output配置中有配置到publicPath: '/public/',则这里应该对应配置成index: '/public/index.html'
historyApiFallback: {
index: '/index.html'
},
2、vue-router嵌套路由404
官方文档提示当使用嵌套路由时要注意,以 /
开头的嵌套路径会被当作根路径。
// routes.js
export default [
{
path: '/',
redirect: '/app'
},
{
path: '/app',
component: Todo,
name: 'app',
meta: {
title: 'this is app',
description: 'app'
},
+ children: [
+ {
+ path: 'test',
+ component: Login
+ }
+ ]
},
{
path: '/login',
component: Login
}
]
hash模式下bundle.js请求路径:
history模式下bundle.js请求路径:
于是我们可以发现问题所在,如图所示,webpack帮我们打包js代码后自动在index.html引入方式为src="bundle.js",即相对路径./bundle.js 。
而根据官方文档所述,以 / 开头的嵌套路径会被当作根路径。因此:
原因:
- history模式——此时的根路径为localhost:8000/app,相对路径使得请求当前根路径下的bundle.js资源,而服务端找不到该资源于是返回404
- hash模式——此时的根路径他同样为/app,但由于向服务器发请求的时候会忽略#后面的值,因此不会发送localhost:8000/#/app/bundle.js而仅仅发送localhost:8000/的请求,此时相对路径使得请求当前路径下的bundle.js资源,请求成功,再由前端路由处理/app/test
解决方式:webpack配置output使得请求bundle.js的路径是绝对路径而不是相对路径
output: {
filename: 'bundle.js',
path: path.join(__dirname, '../dist'),
// 在引入静态资源时,从根路径开始引入,即/bundle.js
publicPath: '/'
},
效果: