Vue history模式刷新页面404问题及解决
作者:梅坞茶坊
为什么会出现404
我们先来看一下我们给到后端的dist
文件
可以看到dist下只有一个 index.html
文件及一些静态资源,这个是因为Vue
是单页应用(SPA),只有一个index.html
作为入口文件,其它的路由都是通过JS来进行跳转
接着我们再来分析一下后端 nginx
的配置
server { // 监听80端口 listen 80; // 定义你的站点名称 server_name website.com; // 根据请求 URI 设置配置 location / { // 站点根目录,这里为 vue 构建出来的 dist 目录 root /www/dist; // 站点初始页为index.html 或 index.htm index index.html index.htm; } }
我们现在可以根据 nginx 配置得出,当我们在地址栏输入 website.com 时,这时会打开我们 dist 目录下的 index.html 文件,然后我们在跳转路由进入到 website.com/login
关键在这里,当我们在 website.com/login 页执行刷新操作,nginx location 是没有相关配置的,所以就会出现 404 的情况
为什么hash模式下没有问题
router hash 模式我们都知道是用符号#
表示的,如 website.com/#/login, hash 的值为 #/login
它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对服务端完全没有影响,因此改变 hash 不会重新加载页面
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 website.com/#/login 只有 website.com 会被包含在请求中 ,因此对于服务端来说,即使没有配置location,也不会返回404错误
单页应用(SPA)概念
我们前面有提到单页应用,那什么是单页应用呢?
单页应用
单页应用(single-page application),缩写SPA 是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。
这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面。
尽管可以用位置散列或HTML5历史API来提供应用程序中单独逻辑页面的感知和导航能力,但页面在过程中的任何时间点都不会重新加载,也不会将控制转移到其他页面
大白话来讲:
一个杯子,早上装的牛奶,中午装的是开水,晚上装的是茶,我们可以发现,变的始终是内容,而容器还是那个容器
当然,每种技术都有其利弊,单页应用也是如此
利:
- 无刷新体验,这个应该是最显著的有点,由于路由分发直接在浏览器端完成,页面是不刷新,对用户的响应非常及时,因此提升了用户体验
- 完全的前端组件化,前端开发不再以页面为单位,更多地采用组件化的思想,代码结构和组织方式更加规范化,便于修改和调整
弊:
- 首屏较长,要在一个页面上为用户提供产品的所有功能,在这个页面加载的时候,首先要加载大量的静态资源,这个加载时间相对比较长
- 不利于 SEO,单页页面,数据在前端渲染,就意味着没有 SEO,或者需要使用变通的方案
Router的实现
为了让大家加深大家对 Router 的理解,这里我们实现一个最简洁的 Router
- hash 模式
核心通过监听url中的hash来进行路由跳转
// 定义 Router class Router { constructor () { this.routes = {}; // 存放路由path及callback this.currentUrl = ''; // 监听路由change调用相对应的路由回调 window.addEventListener('load', this.refresh, false); window.addEventListener('hashchange', this.refresh, false); } route(path, callback){ this.routes[path] = callback; } push(path) { this.routes[path] && this.routes[path]() } } // 使用 router window.miniRouter = new Router(); miniRouter.route('/', () => console.log('page1')) miniRouter.route('/page2', () => console.log('page2')) miniRouter.push('/') // page1 miniRouter.push('/page2') // page2
- history 模式
history 模式核心借用 HTML5 history api,api 提供了丰富的 router 相关属性
先了解一个几个相关的api
- history.pushState 浏览器历史纪录添加记录
- history.replaceState 修改浏览器历史纪录中当前纪录
- history.popState 当 history 发生变化时触发
// 定义 Router class Router { constructor () { this.routes = {}; this.listerPopState() } init(path) { history.replaceState({path: path}, null, path); this.routes[path] && this.routes[path](); } route(path, callback){ this.routes[path] = callback; } push(path) { history.pushState({path: path}, null, path); this.routes[path] && this.routes[path](); } listerPopState () { window.addEventListener('popstate' , e => { const path = e.state && e.state.path; this.routers[path] && this.routers[path]() }) } } // 使用 Router window.miniRouter = new Router(); miniRouter.route('/', ()=> console.log('page1')) miniRouter.route('/page2', ()=> console.log('page2')) // 跳转 miniRouter.push('/page2') // page2
解决404
看到这里我相信大部分同学都能想到怎么解决问题了,
产生问题的本质是因为我们的路由是通过JS来执行视图切换的,
当我们进入到子路由时刷新页面,web容器没有相对应的页面此时会出现404
所以我们只需要配置将任意页面都重定向到 index.html,把路由交由前端处理
还是以 nginx 为例,更多版本的大家可以前往https://router.vuejs.org/zh/guide/essentials/history-mode.html 查看
location / { try_files $uri $uri/ /index.html; }
这里有一个小细节,如果出现真的 404 页面了呢?比如 website.com/notfound
因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ] })
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。