您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Vue-Router + Nginx解决单页应用路由问题
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Vue-Router + Nginx解决单页应用路由问题
自猿其说Tech
2021-07-01
IP归属:未知
169880浏览
前端
### 1 背景 #### 1.1 技术背景 前后端分离,后端提供restful接口,前端为vue2.0框架,路由上使用vue-router(history模式路由),用nginx做反向代理 #### 1.2 问题背景 在做web服务器扩容,或者新部署一个vue项目时,对于nginx配置,我们最常做的事就是“一键复制”,但是稍有差错,很难在不了解这一套原理的情况下排查出错误。 问题描述:在扩容时发现,访问新部署的机器,在地址栏输入除了根目录 url( http://lvnew.jd.com)之外的其他 url (例如:http://lvnew.jd.com/lvnew/list)都会出现错误,并跳转到错误页( https://www.jd.com/error.html);此外,由于前端项目中识别到当url是根目录时,会 redirect到 /stockout/backlog ,因此,出现了这种现象:用户输入 http://lvnew.jd.com =》前端redirect到 /stockout/backlog,此时地址栏中显示 http://lvnew.jd.com/stockout/backlog 页面内容正常显示 =》用户在当前页面刷新 =》错误页 ![](//img1.jcloudcs.com/developer.jdcloud.com/5291dc24-a377-4439-98c3-9e3e3ffc7b4b20210701114148.png) ### 2 分析 #### step1:为什么跳转到错误页 打开控制台,发现 跳转错误页的表层原因在于:发送 http://lvnew.jd.com/stockout/backlog 请求,请求了一个页面资源,这个页面资源是不存在的,正常应该抛出 404 错误,但由于nginx的拦截,并做了重定向,因此返回了error.html页面; ![](//img1.jcloudcs.com/developer.jdcloud.com/7834e56c-ae3e-4454-8b8c-c9c9f5357ab320210701114228.png) #### step2:为什么请求页面资源会报404 正常情况下,当我们想要访问一个页面,也就是想要请求包括html在内的静态资源,第一步会在在地址栏输入了一个地址,比如 http://lvnew.jd.com ,然后会经历DNS解析、三次握手等等一系列过程,最后从服务器拿到这个url对应的html等静态资源。lvnew是一个单页应用的vue项目,页面资源只有一个index.html作为整个项目的入口。当访问根目录是,也就是 http://lvnew.jd.com后面不加任何斜杠的时候,会直接请求到index.html —— 这是正常情况;但当我们在 http://lvnew.jd.com/stockout/backlog 这个位置刷新页面的时候,实际上是去请求了/stockout/backlog这个下的静态资源,在没有定义这个路径访问什么静态资源的时候,当然是会报404的错误; #### step3:为什么有时候刷新页面不会报错 vue项目里,可以分为两种情况 - hash模式的vue-router,如果是vue项目,并且采用了hash的路由模式,那么就永远不会出现这个错误。详解参见官网:https://router.vuejs.org/zh/guide/essentials/history-mode.html **简单来说:** 与hash模式相对的是history模式,也就是lvnew项目里用到的,可能会出现上述问题的模式,两者的区别如下: **hash模式:http://test.com/#/haha** 当hash值改变,页面不会重新请求静态资源,不会重新加载页面 优点:不需要另外的配置,vue-router默认的模式 缺点: url看起来长得比较丑; 在2.8.0以下的vue版本中,跳转路由不会触发hashchange事件,会导致某些依赖hashchange事件的埋点的失效; 在处理一些诸如微信登录、自动登录需要拼接redirect的地址时,#的处理会比较坑 **history模式:http://test.com/haha** 每次改变url都会重新加载页面 优点:没有上述hash的问题,美观 缺点:重新加载带来的性能问题;需要配置 服务端增加配置覆盖所有候选资源 nginx在接到url请求时,根据匹配规则检查是否能匹配到静态资源,如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你我们app 依赖的页面 ### 3 本次问题的解决 顺着step3的思路增加nginx配置,使得history模式下,不管什么请求都只返回index.html 写法一: ```markdown location / { try_files $uri $uri/ /index.html index.htm; } ``` 写法二(注意以下两段 配合使用 ,本次出现问题的原因就在于只复制了第二段,忘记了指定什么规则下执行@rewrites): ```markdown location / { try_files $uri $uri/ @rewrites; } location @rewrites { rewrite ^(.+)$ /index.html last; } ``` ### 4 反思 这么配置会不会出问题:上文说到 “不管什么请求都只返回index.html” 那么我做正常的restful等的请求,想要得到json,是不是也会返回index页面而不是json数据? ##### 不会出问题 原因在于:nginx的匹配规则——优先匹配精准匹配 **举个例子,结合下图:** 请求index页面时,我们的路径就是根地址,匹配到了第 1 条规则,并且下面没有相匹配的规则,那么会返回index.html 请求json数据时,例如后端controller层url为 /lvNew/getResource,前端请求为 Request URL: http://lvnew.jd.com/lvNew/promise/getResource 匹配了第 1 条规则,同时,也匹配了第 4 条规则,那么 第 4 条规则优先,请求被转发到了 http://lv.jd.com/lvNew/getResource 返回正确的json数据 ![](//img1.jcloudcs.com/developer.jdcloud.com/a5341ed4-0d84-4ade-8106-e22eb7888e1020210701114544.png) ### 5 vue单页应用路由配置解决通法 #### step1:vue-router配置 a.vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载 ```javascript const router = new VueRouter({ mode: 'history', routes: [...] // 此处可以用路由地图等的方式列举项目中定义的路由 }) ``` b.如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面 ```javascript mode: 'history' ``` c.abstact模式 作为vue-router的第三种模式,是与浏览器分离的路由模式,本身是用来在不支持浏览器API的环境中,充当fallback,可以实现例如:在已存在的路由页面中内嵌其他的路由页面,而保持在浏览器当中依旧显示当前页面的路由path。这种模式还有可替代的解决方案,例如组件之间引用等,因此并不常用 #### step2:项目中增加404页面 如果选择了history模式,并选择了如下3、4、5中的某一种配置方式,服务器就不会再返回404页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,我们应该在 Vue 应用里面覆盖所有的路由情况,然后再给出一个 404 页面。 #### step3.1:nginx解决方式(使用范围、场景最广) - hash的路由模式下(只需要增加相对路径配置,就能找到index入口): ``` server { # 服务器端口 listen 80; # 服务器名称 server_name localhost; # 路径配置 location / { # 相对路径配置,基于nginx启动的位置 root dist; index index.html; } # 后端接口,反向代理 location ~ /rest { # 反向代理 proxy_pass http://ip:port; } } ``` - history的路由模式下(需要增加其他路由的重定向): 结构和上面一致,区别在于“路径配置”: ```markdown location / { try_files $uri $uri/ @router; } ``` 上述语句的作用是:按顺序检查本地(服务器)文件是否存在,返回第一个找到的文件或文件夹(结尾加斜线表示为文件夹),如果所有的文件或文件夹都找不到,会进行一个内部重定向到最后一个参数。 需要注意的是,只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部URI的指向。最后一个参数是回退URI且必须存在,否则会出现内部500错误。命名的location也可以使用在最后一个参数中。 与 rewrite 指令不同,如果回退URI不是命名的 location 那么 args 不会自动保留,如果你想保留 args,则必须明确声明。 对应上面的@router,这里还需要做一下定义(!important 不能省略): ```markdown location @router { rewrite ^.*$ /index.html last; } ``` 主要原因是路由的路径资源并不是一个真实的路径,所以无法找到具体的文件,因此需要rewrite到index.html中,然后交给路由在处理请求资源。 补充一些语法: last :将得到的路径重新进行一次路径匹配,浏览器地址栏URL地址不变 break:本条规则匹配完成后,终止匹配,不再匹配后面的规则,一般不用,否则会导致下方反向代理的规则失效 redirect: 返回302临时重定向 permanent:返回301永久重定向 对于静态资源,去掉项目名,进行定向 \为转义, nginx 中的 / 不代表正则,所以不需要转义 rewrite (static/.*)$ /$1 redirect; 对于非静态资源,直接定向index.html,一般采用这种写法。 以上配置,还有另外一种写法,可以省去对路由资源位置的定义: ``` 由资源位置的定义: #loation / { # try_files $uri $uri/ /index.html ndex.htm; #} ``` #### step3.2:node.js解决方式 ```javascript 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) }) ``` #### step3.3:Apache的解决方式(不常用) ``` <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule> ``` ### 6 总结 其实 vue-router + nginx 是个老生常谈的问题,但工作中,这类问题还是经常会浪费我们很长时间去排查和解决。究其原因,除了需要经常补充各类知识之外,还需要在每次解决问题之后,及时回顾和总结,摸清里面的小细节~ ------------ 自猿其说Tech-JDL京东物流技术发展部 作者:供应链技术部 何柳
原创文章,需联系作者,授权转载
上一篇:GraphQL实战(2)-java和spring boot实现
下一篇:Flutter之Image加载流程源码解读
相关文章
前端十年回顾 | 漫画前端的前世今生
Taro小程序跨端开发入门实战
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号