教你不花一分钱,让前端SPA应用发版不白屏!

相信不少前端开发的同学都有做过SPA项目的经验,例如使用React、Vue或者是一些SSR框架,例如Next、Nuxt等都是可以实现SPA应用的。

背景

当然作者所在的项目团队也是在做SPA应用,那么最近就遇到一个可能大家都遇到过的问题,就是SPA项目在更新发布后,因为构建资源都是使用文件md5进行命名,所以就会存在一种情况,用户长时间打开SPA应用,而在这个时候我们的SPA应用进行发布更新,这时候用户浏览器中的SPA路由表还是旧的,就会出现用户长时间停留后,再次使用进行SPA跳转去加载页面资源时,新旧版本的文件名不一致导致渲染异常等情况。那么应该如何解决呢?本篇文件将告诉你最省钱的解决方案。

什么是SPA?

本篇文章不详细说明什么是SPA应用,有兴趣的可以自行搜索。以下是简单的概括:

单页应用(英语:single-page application,缩写SPA)是一种网络应用程序网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页应用中,所有必要的代码(HTMLJavaScriptCSS)都通过单个页面的加载而检索[1],或者依需(通常是为响应用户操作)動態裝載适当的资源并添加到页面。尽管可以用位置散列HTML5历史API来提供应用程序中单独逻辑页面的感知和导航能力,但页面在过程中的任何时间点都不会重新加载,也不会将控制转移到其他页面。[2]与单页应用的交互通常涉及到与网页服务器后端的动态通信。维基百科

问题的本质是什么?

解决问题前,需要先搞清楚出现问题的本质是什么?在SPA应用发版更新后,出现资源加载异常、渲染异常和白屏等现象的本质是SPA应用内部一定会有一个路由表去记录每一个页面所对应的资源,其中基本包括这个页面所依赖的js和css。哪怕是Next和Nuxt也不例外。

添加图片注释,不超过 140 字(可选)

这是一个最简单的SPA应用,在浏览器切换页面的流程图。当然这是一个极度简化后的示意图,仅仅表达在前端路由中进行页面切换时,会通过路由表去获取目标页面的资源,从而决定应该如何去渲染。

那么基于以上的流程加入版本的概念时,当我们进行版本发布时,就可能会存在用户因长时间停留没有刷新页面,导致前后端的路由不一致的问题。

添加图片注释,不超过 140 字(可选)

因为发布了新的版本,所以旧版本的资源已经被删除,这时候因为用户长时间打开页面,也不刷新,这时候用户在浏览器中的路由表指向的还是旧的资源,这时候用户在没有任何缓存的情况下,就会出现资源加载失败的情况。

而新用户,或者刷新过的用户,重新从服务端拉取代码,这个时候服务端返回的是版本B的路由表。所以一切正常。

通过以上流程大致已经分析出来问题的根本原因有以下几个:

  1. 发布新版本后,旧版本的资源在服务器不存在,导致加载异常。
  2. 用户长时间打开SPA应用挂机,导致浏览器中的路由表资源依赖与服务器不一致。
  3. 客户端和服务器之间版本没有一个同步的机制。

如何解决这类问题?

其实网上都有各种解决方式,但是在我看来每一种方式都或多或少有一些问题,这里我将我能找到的一些解决方案做一个简单的总结。

CDN部署方案

CDN部署方案可以说是最简单直接的方式,将前端构建资源打包后推送到云供应商的存储桶并开启CDN加速,实际上就是将不同版本的资源都存储到外部,那么客户端无论是哪个版本,只要存储桶中有这个资源,并且没有因为定期清理过期文件而被清理掉,理论上可以SPA应用的前后端版本不一致而导致的资源加载问题。

但是这里要考虑以下几点:

  1. 无论是存储桶还是CDN加速,都是要钱的。
  2. 其次可能有一些项目不具备上CDN的条件,例如一些内部安全项目这类对于资源存放有较强的安全要求,就不可能托管到第三方平台做存储。

CDN能解决问题,但是并不能覆盖所有场景。要钱!要钱!要钱!

资源增量部署

资源增量部署的方案一般来说只适用于使用非Docker部署的项目,例如以前我们常用的将资源打包成一个zip包,通过脚本批量推送到我们的服务器,然后进行解压。 这种方式就是让新旧版本的资源同事存在,并不会因为发布新版本而删除旧版本的资源文件。

但是这种方式有很明显的弊端:

  1. 首先现在逐步走向容器化部署,越来越少直接推送文件到服务器。当然就算容器化也可以这么干,只需要给容器绑一个持久化的数据卷,那么也是可以实现。
  2. 随着时间推移,资源文件会一直堆积,最终会撑爆硬盘空间,需要有定时脚本做清理

可以解决问题,但是不太符合发展趋势。并且需要设计资源的清理策略。

监听资源错误强制刷新

该方案属于纯前端的解决方式,通过监听script和link的onerror事件来实现。当发现资源加载失败,并且状态码404,就通过js强制刷新浏览器,来达到强制同步版本的目的。虽然可以解决问题,但是明显这种方案体验非常不好,很容易会造成一种现象就是用户进行跳转->白屏->突然刷新。

比较粗暴的解决方式

版本轮训

该方案是通过每次发布版本时生成一个唯一的版本号,并且注入到页面中,而页面设置一个定时器,定时轮训后端接口,而后端接口对服务端的版本号好客户端的版本号进行比对,发现不一致时返回约定的状态码或者body给到前端,前端出提示框提醒用户刷新。

这里虽然比监听资源错误强制刷新在体验上稍微好一丢丢,但是本质其实还是需要用户刷新,如果用户不刷新,依然还是白屏。

前后端同时改造成本大,体验对比《监听资源错误强制刷新》好一些,但是并没有解决问题。

最省钱的方案来了

终于要说我提出的最省钱的方案了,当然如果你所在项目不缺钱,有条件上CDN,还肯定是上CDN。但是如果不行,不妨考虑一下我的方案。

我的项目是使用Next开发的,所以这里给出一个大概的架构图,当然其他项目一样可以套用该方案,因为这个方案根本不需要前端改造任何代码。

添加图片注释,不超过 140 字(可选)

这里其实就是利用了Nginx的proxy_cache来实现对静态资源的持久化缓存。在每次经过Nginx读取静态资源后,都会缓存一份在Nginx服务器本地,下次再有请求相同资源时,如果设置的缓存时间没有过期,那么Nginx将直接从本地返回,不需要经过后端服务器。

使用这种方式,即使后端服务已经进行过发版升级,但是因为Nginx本地存在缓存,所以直接从缓存中读取文件即可。使用这种方式,无论你的项目架构是使用Next这类SSR框架,还是单纯的React、Vue实现SPA,资源在后端服务中的情况。又或者使用k8s或者服务器虚拟机部署你的服务,这套方案都适用。

而且最重要的是不用多花一分钱,在原有架构上稍加改造即可完成。

甚至连网络硬盘都不用,直接使用服务器本身的硬盘空间即可。

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

那这一套方案有没有缺点呢?

那肯定有的,例如Nginx中没有缓存到一些页面的资源,导致SPA应用升级后,用户访问到了这些资源,也会造成资源找不到的问题。那么这里其实就可以结合一些其他手段来处理这种问题了。

  • 比较简单的方式就可以通过 《监听资源错误强制刷新》或者《版本轮训》来做兜底。

  • 当然也可以实现一个脚本,将构建输出的资源做个记录,然后做个预热的功能,在发布后通过脚本把构建的资源做一次访问,让Nginx都缓存一遍,那么就不存在资源没有被用户访问而导致资源没有缓存的问题了。

最后

欢迎补充一些奇思妙想的方法来扩充一些解决方案。 没有最好的方案,只有最合适的方案。

相关推荐
熊的猫几秒前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
速盾cdn7 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水40 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust1 小时前
css:基础
前端·css
帅帅哥的兜兜1 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园1 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称2 小时前
购物车-多元素组合动画css
前端·css