美文网首页
nginx 发布前端资源的缓存方案

nginx 发布前端资源的缓存方案

作者: 纯爱枫若情 | 来源:发表于2021-12-18 18:03 被阅读0次

    前言

    对于前端开发者来说,最头疼的事情莫过于。当你兴冲冲的给项目打包以后,上传到 nginx 等静态资源服务器发布以后,自己本地验证没问题,以为万事大吉,结果测试人员、用户反馈系统没更新,让你顿时感觉如同吃了苍蝇般难受。

    没办法,作为一个专业的开发人员,用户反馈程序出问题,第一时间就要找到问题出现的原因。

    于是你开始进行检查确认工作,先是打开浏览器访问系统发现没问题,确实是更新以后的版本。然后你开始对自己更新的过程产生了怀疑,上服务器,检查一下更新以后的资源文件,再三的详细比对,发现确实没问题后,你忽然恍然大悟,原来代码确实没问题,有问题的是浏览器缓存。

    解决问题思路

    前端打包后的文件,其实就是一些静态资源,我们一般情况下,是直接放在静态资源服务器提供服务的。

    一般,解决发布资源缓存这个问题,有几种思路:

    1. 浏览器设置每次都重新下载页面的所有的资源。

    这个方案有个优点,每次拿到的资源都是重新拉去的最新的资源,所以不存在会缓存的问题。

    但是这个方案的缺点非常明显,它直接让缓存失去了自身的意义。没有缓存,增加了服务器的负担,对服务器不太友好,同时也让用户使用网页的体验降低了。

    而且,默认情况下,没有浏览器会这么设置。如果需要用这个方式,就需要引导用户这么设置,同时也对用户要求太高。

    所以这个方案,不用考虑,直接就可以 pass 掉了。

    2. 在静态资源服务器上配置,强制不让浏览器进行缓存。

    常见的静态资源服务器,像 nginx、apache 等,确实可以配置让客户端永远不缓存静态资源。

    虽然与第一个方案相比,它存在一些优点,比如不需要引导用户对浏览器进行配置。

    但是弊病和第一种思路一样,对用户和服务器都极其不友好,所以这个方案也可以被 pass 掉了。

    3. 最好的解决方案

    其实有一种比较好的解决方案,需要前端和静态资源服务器配合起来达到。

    前端资源其实分为几类:

    1. 内容会变化,但是文件名不会变,用户也只能通过某个路径访问到,最典型的就是页面入口的 index.html 文件
    2. 内容会变化,文件名也可以变化,比如每次打包生成的 js文件、css 文件、图片资源等
    3. 内容不会变化,文件名也不会变化,比如直接在 html 中引入的第三方包

    针对这几种不同的解决方案,可以针对性的作优化。

    第一类,可以采用 Etag 的缓存策略。

    如果没了解过这个名词的童鞋,可以 mdn 上了解下相关知识:ETag - HTTP | MDN

    简而言之,如果采取了 Etag 策略,那么每次请求某个资源的时候,服务器会在响的的请求头里返回 Etag 标签:

    image

    下次,如果再请求同样的资源的时候,浏览器就会在请求头里带上这个 Etag,让服务器进行对比,如果 Etag 能够匹配上,就返回 304,浏览器就明白,不用重新下载资源,直接用本地缓存就行;否则浏览器会返回200,并返回完整的请求资源。

    image

    比如,想要在 nginx 中配置 etag,可以参考下面的链接:

    Module ngx_http_core_module

    直接这样配置即可,同时要在 header 中加上 cache control 控制,让浏览器对 html 文件应用 no-cache 规则。

    location ~ .*\.(?:htm|html)$ {
       ...
       add_header Cache-Control "no-cache";
       etag on;
       ...
    }
    

    不了解 cache-control 的童鞋,可以参考下 mdn 的文档:
    Cache-Control - HTTP | MDN

    这样配置了以后,对于所有的 html 文件,就达到了当文件没更新的时候,返回 304,当文件更新了,自动拉取最新的资源的目的。

    第二类,直接采用强制浏览器缓存的方法。

    对于第二类资源,我们可以通过添加 hash 的方式保证,即使前端版本更新了,文件名也不一样,那么我们就不需要像第一种方式一样,通过 Etag 方式去缓存了。

    因为即使是 Etag 策略,其实也是会消耗请求资源的。对于这种发布了就不会更改的内容,直接可以通知浏览器长期缓存即可。

    想达到这种效果,直接通过配置 cache control header 即可。

    location ~ .*\.(?:js|css|jpg|jpeg|gif|png)$ {
       ...
       add_header Cache-Control "public, max-age=31536000";
       add_header Last-Modified "";
       etag off;
       ...
    }
    

    直接配置对于第二类资源,关闭 Etag,同时设置强制缓存,同时设置上一年的缓存期。

    这样设置了以后,当浏览器再次请求该资源时候,会查看浏览器之前缓存的该资源是否还在有效期以内,如果在,便会直接应用该资源,不会向浏览器再次发起请求了;否则,会重新发起请求,请求该资源。

    image

    第三类资源的解决方案,其实跟第二类一样,通过添加 hash 值或者 timeStamp,用以区分资源,达到每次更新版本后,请求的资源不一致。

    对于这两类资源的处理,在 nginx 上的配置,也并无差别,可以等同对待。

    对于前端来说,就比较麻烦一些。

    下面列举一些,在常用的前端框架的脚手架中,该如何配置,以达到自动添加 timeStamp 的效果吧。

    • create-react-app

    在 react 这个官方脚手架中,支持从环境变量传值: Adding Custom Environment Variables | Create React App

    所以,我们直接在模板中,替换调传入的值即可。

    /** .env 文件 */
    REACT_APP_WEBSITE_NAME=测试标题
    REACT_APP_TIMESTAMP=1639814656911
    
    /** html 模板文件,通过传值,替换掉模板中的内容 */
    <title>%REACT_APP_WEBSITE_NAME%</title>
    <script src="%PUBLIC_URL%/config.js?timeStamp=%REACT_APP_TIMESTAMP%"></script>
    
    • vue-cli

    在 vue 脚手架中,可以通过重载 webpack 配置,来设置变量。

    /**
     * @type {import('@vue/cli-service').ProjectOptions}
     */
    module.exports = {
        chainWebpack: (config) => {
            config
                .plugin('html')
                .tap((args) => {
                    args[0].timeStamp = Date.now();
                    return args;
                });
        },
    };
    

    然后通过在 index.html 模板中,采用 lodash template 的方式,应用变量。

    <script src="<%= BASE_URL %>config.js?timeStamp=<%= htmlWebpackPlugin.options.timeStamp %>"></script>
    

    详细用法可以参考官方文档:HTML and Static Assets | Vue CLI

    后记

    其实对于同一个问题,往往会有多种不同的解决方案。有时候多研究一下,多思考一下,综合考量,就可以找出一种比较好的解决方案。

    相关文章

      网友评论

          本文标题:nginx 发布前端资源的缓存方案

          本文链接:https://www.haomeiwen.com/subject/xxvgfrtx.html