likes
comments
collection
share

记一次前端应用部署后,刷新静态资源丢失问题

作者站长头像
站长
· 阅读数 10

一、前言

最近在生产环境发现一个很奇怪的现象,当更新生产环境版本时,就监控到部分用户js资源出现如下提示,但是刷新后发现页面访问正常。

基本就是这个Issues的描述信息

记一次前端应用部署后,刷新静态资源丢失问题

发现了这个问题后,首先怀疑,服务器前端资源B版本更新后,用户浏览器缓存了入口文件index.htmlA版本,index.html拉取A版本js资源,但B版本更新后A版本资源不存在导致的。

给入口文件增加强制不缓存后,发现还有个别用户出现这个问题。

这还了得,赶紧查查,但总是偶现并不能随时重现,开始怀疑是nginx转发的问题,后来经过排查发现:

当前端组件A为懒加载,且用户保持页面在线且没有点击该组件A,此时更新版本后,用户点击A组件路由,浏览器拉取静态资源404报错。

所以因为浏览器缓存的存在,要避免前端资源的直接覆盖,容易导致各种js找不到的情况出现。

二、模拟环境准备

1、一个前端项目

这里的前端项目通过create-react-app来搭建,路由通过懒加载方式实现,核心代码如下:

import { Suspense, lazy } from 'react';
import { HashRouter, Routes, Route, Link } from "react-router-dom";
const AFunction = lazy(() => import('./page/a.js'))
const BFunction = lazy(() => import('./page/b.js'))

function App() {

  return (
    <div className="App">
        <HashRouter basename="/">
          <div style={{marginBottom: 20}}>
            <Link style={{marginRight: 20}} to="/">Home</Link>
            <Link to="/about">About</Link>
          </div>
          <Suspense fallback={<div>Loading...</div>}>
            <Routes>
              <Route path="/" element={<AFunction />}></Route>
              <Route path="/about" element={<BFunction />} />
            </Routes>
          </Suspense>
        </HashRouter>
    </div>
  );
}

export default App;

2、本地安装Docker服务

生产环境一般使用Nginx部署,我们在本地复现,采用nginx + docker来模拟。

三、重现生产环境问题

1、通过nginx正常部署前端服务

docker pull nginx
docker run -d 80:80 -v /Users/user/Desktop/my-app:/usr/share/nginx/html nginx

如图所示,我们启动了my-app后,当点击About时,可以看到加载了About的资源,而不是一开始就全部加载。

记一次前端应用部署后,刷新静态资源丢失问题

重现问题操作

刷新当前页面,保持页面位于Home页面,不要点击About。此时加载了前端资源但没有加载About相关的资源。

2、更新版本发布后,复现问题

修改About组件内容(注意要修改待测试的组件,防止由于组件hash没有更新,无法重现问题),重新打包build文件

import React from 'react';
function BFunction(){
    return <>
        b组件3
    </>
}
export default BFunction

重新启动Docker,查看index.html静态资源引入

docker restart [containerId]

记一次前端应用部署后,刷新静态资源丢失问题

此时打开原来的页面,不要刷新,直接点击About组件,发现资源加载404,页面空白

记一次前端应用部署后,刷新静态资源丢失问题

此时刷新当前url或者重新打开url,发现页面加载正常

记一次前端应用部署后,刷新静态资源丢失问题

以上就重现了我们生产环境遇到的问题,更新版本个别用户访问页面出现空白。

四、分析并解决问题

1、解决思路

问题的原因,我们知道了,那么解决问题的方案有以下几种

  • 用户更新资源
    • 1、每次切换页面,都重新加载入口文件,这样每次都能加载到最新的静态资源。
    • 2、当版本更新后,提示用户重新加载站点
  • 保留服务器老版本资源
    • 3、既然浏览器有缓存,还要加载原来的资源,那么就保留多个资源版本,这样当用户在浏览系统时,即使服务器更新了版本,用户只要不刷新页面,那么访问的依旧是老的版本

以上方案的优缺点

  • 1、方案一明显浪费了单页面应用加载时间的优越性,属于自废武功。
  • 2、方案二更新版本提示用户,对用户过于打扰。
  • 3、方案三无疑是最好的方案,保存多个版本,还能做灰度方案,在出现问题时及时回退。
  • 4、你还有什么建议吗?

3、服务器资源版本配置

具体实现,首先要改造项目,让项目构建时打包成不同的版本,即修改文件的打包路径,由于我们的项目是通过create-react-app实现的,那么更新打包路径就要释放webpack配置或者利用插件

我们部署nginx的访问静态服务器/data/web文件目录为

- 2023080101
    - static
        - css
        - js
- 2023080102
    - static
        - css
        - js
- 2023080201
    - ...
- index.html
- logo.png

当我们构建新的版本时,只需要在项目下新增一个版本,然后更新index.html,这样当老的index.html切换路由,可以拉取到老的资源文件,新的也可以使用新的资源。

3、改造前端服务

要实现上面的方案,就要改写项目,改写方式很多,只需要选择一种即可。

1)安装依赖和修改webpack的插件

yarn add react-app-rewired customize-cra -D

2) 修改webpack配置文件

在项目根目录下新建文件config-overrides.js

module.exports = function override(config, env){
  config.output = {
    ...config.output,
    // 保持开发环境访问路径不变,生产环境改变打包路径
    publicPath: env === 'production' ? `/${process.env.BUILD_PATH}/` : '/'
  }
  
  return config;
};

3)修改script指令

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
}

4)管理打包的版本号,方便后续自动化构建和统一管理

在项目下新建.env文件统一管理版本

BUILD_PATH = "2023080101"

4、部署第一个版本

打包前端服务,打开index.html,发现引入资源路径更新了

记一次前端应用部署后,刷新静态资源丢失问题

重新Docker部署,注意js路径变成了2023080101下面

记一次前端应用部署后,刷新静态资源丢失问题

5、部署第二个版本

修改.env版本号,重新构建,将打包的文件放在Nginx服务目录下,然后重启Docker

BUILD_PATH = "2023090101"

重新上一步的页面,点击About发现加载正常,资源是之前的2023080101 记一次前端应用部署后,刷新静态资源丢失问题

这样就解决了上面提到的问题,接着刷新当前页面,查看加载的资源,发现资源已经更新成2023090101

记一次前端应用部署后,刷新静态资源丢失问题

五、总结

以上,我们解决了更新版本资源丢失的问题,这个解决方案还是比较好的。而且后续可以支持我们做灰度的管理、版本的控制、回退处理

他的难点在:

  • 1、需要对项目进行改造,构建不同的版本
  • 2、需要将资源放在对应的目录下,如果手动操作就比较繁琐

解决方案:

  • 1、建议接入CI/CD,这样可以将流程固定化,版本号通过时间戳来记录。
  • 2、备份index.html,通过脚本实现回退能力。
转载自:https://juejin.cn/post/7269694294597287971
评论
请登录