ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

前置资源与缓存:浏览器性能优化的底层红利

前置资源与缓存:浏览器性能优化的底层红利 如果说前端优化里有一门“花小钱办大事”的手艺我第一个想到的就是前置资源和缓存。这不是什么花哨的框架也不是某个新出的构建工具而是浏览器本身就给我们留好的性能红利。很多团队抱怨“页面首屏慢、二次访问还是慢、CDN流量居高不下”查来查去问题往往不在代码执行而在资源到底有没有被提前准备、有没有被合理复用。这篇内容是架构师系列里“前端优化思路”的一个核心分支适合正在往架构师方向走的开发者、带团队做性能优化的前端负责人以及所有已经被“点击—白屏—加载—渲染”流程折磨过的人。你会看到一条请求从输入URL到页面渲染到底把钱花在了哪里也会看到前置资源、HTTP缓存、Service Worker这几层是怎么叠加出“秒开”效果的。我见过太多“三流架构师”式的缓存方案——全站资源一律max-age31536000然后每次发版就手动刷新CDN、让用户强制刷新。真正的架构师会先搞清楚每类资源的生命周期、每层缓存的失效机制再把它们串成一张网。这一篇就是把这张网摊开给你看。1. 架构师视角为什么“前置资源缓存”是性价比最高的前端优化1.1 一条请求的生命周期从“花钱”说起我们先别看代码先看一条普通请求从用户浏览器发出到页面渲染中间经历了什么。这个问题我在面试架构师候选人时几乎必问能完整答出来的不多。整个过程粗略拆开是DNS解析 → TCP建连 → TLS握手 → 发起HTTP请求 → 服务端处理 → 返回响应 → 浏览器下载资源 → 解析HTML/CSS/JS → 执行渲染。每一步都有时间成本而且很多步骤是不能并行的。DNS解析在极端情况下要几十毫秒甚至上百毫秒TLS握手在大厂全站HTTPS的背景下少说也要一两个RTT再加上服务端处理时间一个没有任何缓存优化、也没有资源预加载的页面首屏出来个三五秒非常正常。把这个过程换成生活场景就好理解了。你点了一份外卖从下单到吃上中间有商家备餐、骑手取餐、配送、你开门取货。如果一份外卖能提前知道你几点会饿在你还没点单的时候就开始备餐后面当然会快很多。如果这顿饭你上周刚点过一模一样的、商家还保留了原料那大概率连重新做的功夫都省了。前端优化里的前置资源就是“在你还没下单的时候商家已经提前把菜备好”缓存就是“上次做过的菜这次直接端上来不用再重复下锅”。这俩都是同一个思路把花钱的动作提前或者让已经花过的钱不重复花。1.2 前置与缓存的本质提前花钱和少花钱前置资源和缓存的本质区别一个在于“时间”一个在于“次数”。前置资源解决的是关键路径问题页面首屏必须等某些资源下载完才能渲染那我们就想办法让这些资源在真正需要的时候已经从网络上下载完了。比如图片懒加载之外还有“预加载”的思路字体文件在CSS解析到之前就先下载再比如用户鼠标悬停在某个导航项上时提前把这个路由的JS和接口数据拉回来等用户点进去时已经是“瞬开”。缓存解决的是重复劳动问题同一个资源第一次访问从服务器完整下载第二次访问如果服务器说“这东西没变你用本地那份吧”浏览器就直接从磁盘或者内存里取。这省掉的不仅是下载时间还有网络往返的延迟和服务器带宽成本。我把两者整理成一张对照表做方案时脑子里先立住这张表手段核心做法解决的问题类比前置资源preload、prefetch、preconnect、数据预取关键路径上的等待提前备菜HTTP缓存Cache-Control、ETag、CDN缓存同一个资源的重复下载保留剩菜热一下就能吃应用层缓存Service Worker、IndexedDB、内存缓存离线可用、跨会话复用把菜打包进冰箱存着失效治理版本号、TTL、主动刷新更新时旧缓存挡住新版本定时清理临期食材架构师考虑的不只是“让它变快”还要考虑“怎么失效”。缓存最大的问题不是没有而是失效不可控。这一篇后面的内容会大量围绕“如何让缓存稳定地快、安全地失效”展开如果你发现团队里有人在缓存上出了问题通常不是缓存用少了而是失效设计没跟上。1.3 没做过这套优化前你的页面时间都浪费在哪了我刚带团队做性能专项时第一件事就是在测试环境挑了一个业务页面把它的加载链路完整拆了一遍数据大致是这样阶段耗时说明DNS解析30ms本地DNS缓存没命中真实解析TCPTLS建连120ms一个RTT握手两个RTT TLSHTML下载80ms服务端返回较快CSS/JS下载900ms10个静态文件无并行瓶颈却也没缓存字体加载300ms本应早点建连却在渲染时才请求接口数据600ms首屏三个串行请求浏览器解析渲染400ms页面复杂JS执行占了大头合计约2.4s用户感知比这更久因为分帧卡顿这个结果很典型它告诉我们几个优化方向如果字体和首屏API能在HTML请求的同时就开始建连和预取那900ms的下载和600ms的接口可以压缩一半以上如果静态资源能命中强缓存二次访问时那900ms直接消失。答案其实不在“把代码改得更好”而在“让资源和网络干更少的活”。这就是前置资源和缓存能大幅提升真实体验的原因。2. 前置资源把“未来要用的”提前准备好2.1 preload 与 prefetch什么时候用什么时候别用前置资源最基础的操作是link relpreload和link relprefetch很多同学听说过却一直用不明白。我先把结论放前面preload是“这个资源当前页面马上就要用请优先下载”。它适合当前路由的渲染关键资源比如首屏的骨架样式、核心JS、字体文件。prefetch是“这个资源未来可能会用请有空了再下载”。它适合下一跳路由的资源、低优先级的图片、将来才需要的模块。用法上preload的基本写法是link relpreload asstyle href/css/app.8f2c.css link relpreload asfont typefont/woff2 href/font/iconfont.woff2 crossorigin有几个容易踩的坑我提一下。第一as不能写错。它是给浏览器提示资源类型的写对了浏览器才能正确设置优先级、复用现有连接、匹配Content-Type写错了浏览器的控制台还会报“was preloaded but not used”之类的警告。第二字体文件必须带crossorigin属性就算你的字体和网站同源也要带。因为字体请求本身走的是CORS模式不带这个属性会导致重复下载白白浪费一次请求。preload 最大的风险是“过度使用”。我见过把首屏所有图片、所有模块JS全都preload的页面结果浏览器高优先级通道被占满真正关键的样式反而排队。preload的本质是“插队”插队的人太多原本的队就乱了。所以preload只建议用在当前页面真正阻塞渲染的Top 3资源上。prefetch的槽点在于“猜错了就是浪费”。比如我把详情页路由prefetch了但用户很可能根本不点详情页那几百KB流量就白花了。所以在做prefetch决策时我会看两个指标用户在这个页面的点击率以及跳转到目标页面的转化率。点击率高、跳转占比大的场景才值得做。工程化层面如果你是Webpack 5或Vite用户可以利用构建工具自动注入preload/prefetch。以Vite为例它在构建时默认对首屏的入口 chunk 生成preload你要手动控制时可以借助vite-plugin调整preload的路径列表。用工程化方式管理而不手写link是为了避免发版后文件名哈希变化导致link失效的问题。2.2 preconnect 与 dns-prefetch抢回跨域建连的时间比preload更“轻”的前置手段是提前建连。它不下载任何资源只是提前把网络连接建立好等真发请求时省掉DNS、TCP、TLS的时间。实际优化中我最常用的是这一组link relpreconnect hrefhttps://api.example.com link reldns-prefetch hrefhttps://api.example.com为什么要两个一起写因为preconnect会尝试完成包括TLS在内的完整连接但有些老浏览器不支持dns-prefetch则是一个更广兼容、只做DNS解析的降级方案。两者一起写新浏览器走完整建连旧浏览器至少把DNS解析做了这是典型的“渐进增强”。什么时候值得做判断标准很简单只要首屏有跨域的HTTP请求而且你知道目标域名是稳定的就值得写。最常见的受益方是三类接口服务器、CDN资源域名、字体文件域名。做运维的同事应该都理解一个公式跨域请求的握手开销至少是一个RTT加一个TLS RTT。国内网络环境下一个RTT按30~50ms算两步合一等于省了60~100ms。如果你页面里同时有5个不同域名的资源再叠加浏览器对同一个域名并发连接数的限制这个优化的量级还会放大。还有一点要注意preconnect和dns-prefetch不要无脑把所有第三方域名都填写一遍因为建立连接本身会占用浏览器资源。如果你写了一个连接池但页面里几乎没有请求这个域名的场景这等于白白浪费。还有统计、监控类SDK的域名特别适合做dns-prefetch这些请求不阻塞渲染但做了之后上报成功率会提升。2.3 数据层前置Hover 预取与关键接口预加载资源的前置讲完很多人会忽略另一个大头接口数据的前置。举一个真实场景电商列表页用户把鼠标悬停到某个商品卡片上大概率下一步要点击进入详情页。如果我们等用户真的点下去了浏览器才开始加载详情页的JS再发接口请求那这个白屏时间至少两三百毫秒。更聪明的做法是监听mouseover在悬停事件后通过prefetch拉起详情页路由的chunk同时调用一个轻量的详情接口把数据打到内存缓存里。真跳转时JS已经有了数据也有了页面渲染就是本地工作体感接近瞬开。我自己在一个内容社区项目里做过类似优化资讯列表页的卡片支持mouseover时预取详情数据和文章正文上线后详情页的平均跳转后渲染耗时从原来的750ms掉到180ms左右。而且因为预取是并发做的不占用户点击后的关键路径用户完全感知不到“提前请求”的发生。数据前置有几个前提条件不是所有场景都适合接口必须是幂等的。预取发了一个GET请求用户点击后又发一次后端不能因为重复请求而返回异常。要有合理的缓存TTL。预取到的数据如果在内存里放太久用户点击时读到的可能已经是过期数据。我一般直接用一个5~20秒的Map缓存或者依托请求工具的缓存层控制。权限校验不能漏。预取请求同样要携带用户身份不能让一个匿名用户提前把登录后的数据拉下来存在本地。这类数据泄漏在低级别团队里经常出现。再补一个“查询参数前置”的真实优化首屏如果有关键接口依赖某些慢查询参数比如用户地理位置可以先用浏览器Geolocation API在页面空闲时把定位请求发出去待真正需要时参数已经就绪。说白了前置资源的思想不只是“把JS提前下载”而是“把一切可能同步阻塞关键路径的事情提前在空闲时间做”。3. 缓存参数实战强缓存、协商缓存与CDN分层3.1 Cache-Control 与 ETag先看懂这几个字段再动手HTTP缓存是所有缓存体系的地基也是我几乎每次评审前端代码都会翻一遍响应头的地方。很多团队在这里糊里糊涂最后只能靠“发版本就强制刷新”过日子。强缓存的核心字段是Cache-Control它有几个关键取值需要区分max-age31536000自响应生成起这个时长内浏览器直接用本地副本不发请求。s-maxage31536000和max-age类似但只对共享缓存CDN、代理生效浏览器会忽略它。no-cache不是禁止缓存而是“使用前必须向服务器验证是否新鲜”也就是强制走协商缓存。no-store真正意义上的完全不缓存任何环节都不能存。public/private允许谁存。private表示只能浏览器缓存不能进CDN。immutable告诉浏览器“这个资源在过期时间内绝对不会变”刷新页面时也不用重新验证。它一般只搭配带内容哈希的文件名用。协商缓存是另一个维度。服务器返回ETag或Last-Modified浏览器后续请求带上If-None-Match或If-Modified-Since服务器判断没变就返回304浏览器继续用本地副本。区别在于强缓存连请求都不发协商缓存至少还要发一个请求过去只是省了响应体。实际工程里要在响应头里同时看到Cache-Control: no-cache和ETag这是非常健康的组合——内容一变就更新没变就304。而最恶心的配置是只有短max-age、没有ETag也没有Last-Modified很多后端同学为了“看起来严谨”给所有接口都返回Cache-Control: no-store结果前端性能寸步难行。一句经验接口的缓存策略应该由业务属性决定不能一刀切。能公用的基础配置数据省份列表、字典表可以缓存5分钟甚至更久而强一致性的订单状态就不该缓存。架构师在这个环节的核心价值是帮团队把接口按“可缓存性”分类而不是让每个开发自己拍脑袋。3.2 不同资源的TTL怎么定一张表记住我的默认策略配置缓存时最常被问的问题就是“这个资源到底缓存多久”我的习惯是给一套相对固定的基准业务再按具体情况加减。下面是经过多个项目验证的默认策略资源类型Cache-Control说明HTML入口no-cache必须每次校验才能及时拿到新版本引用带hash的CSS/JSpublic, max-age31536000, immutable文件名变内容变不变就永远用本地缓存图片/字体带hashpublic, max-age31536000, immutable同上基本属于永久缓存图片/字体不带hashpublic, max-age86400一天以内一般够用超出会有更新不及时风险基础配置类接口public, max-age3005分钟适合省市列表、枚举字典用户弱一致接口private, max-age30比如购物车数量这类还算新鲜的数据订单等强一致接口no-store绝对别缓存任何中间层都不许存有人看到“带hash的资源缓存一年”会担心那CDN上旧的缓存会不会一直占用空间其实不会。带hash的文件版本一更新新文件名就是一个新资源CDN上的旧文件会按时间淘汰。浏览器端更简单旧文件的引用只存在于旧HTML里新HTML不再引用它存储引擎会按LRU策略慢慢清理。这里有个重要的参数推导过程为什么带hash资源可以max-age一年因为内容的“版本”已经写在文件名里了。app.8f2c.css和app.9a71.css是两个完全不同的key。只要文件名不变浏览器拿到的就是同一份内容缓存多久都安全。反过来不带hash的接口或图片不能这么做因为同一个URL下内容会变过期时间设太长会导致用户看到旧数据。3.3 CDN缓存与回源策略前端不能只交给运维架构师做缓存方案时前端同学最容易忽略的环节是CDN。但实际上很多静态资源和接口都会经过CDN即使你的部署偏Serverless前面的边缘节点也可能对响应做了缓存。CDN缓存的核心参数就是前面提到的s-maxage。举个例子后端接口返回Cache-Control: public, max-age60, s-maxage600这句话的意思是浏览器本地缓存60秒CDN边缘节点缓存10分钟。用户在1分钟内重复访问连请求都不发CDN节点在10分钟内收到相同URL的请求直接拿边缘缓存返回不回源。设计CDN缓存时要考虑两件事缓存key和主动刷新。缓存key比较复杂常见的问题是URL里带无关参数导致缓存命中率下降。例如?utm_sourcead这种营销参数如果不做忽略处理同一个资源会因为参数不同被当成无数个新缓存CDN命中率会非常难看。这时要在CDN配置里做参数过滤把utm_*这类无关参数在缓存计算时去掉。主动刷新则是架构师必须和运维对齐的流程。资源发版后如果文件名带了hashCDN一般不需要主动刷新天然是新URL。但HTML、接口这类固定URL的内容更新后CDN节点可能还在用旧缓存需要主动调用平台API做purge或者等s-maxage自然过期。实际操作里我见过“上线2小时用户还在看老版本”的故障根源往往是CDN上s-maxage86400但发版流程里漏了purge。一句话总结这层设计前端、后端、CDN三方必须对同一份缓存头有统一的解释写缓存的人要清楚谁会读到这份缓存会缓存多久以及失效规则是什么。4. Service Worker 与应用层缓存真正的“零网络”加速4.1 Workbox precache把 App Shell 秒开的机制讲透HTTP缓存虽然收益巨大但它的短板是必须依赖网络通信。哪怕命中了强缓存浏览器也要先确认这个URL在自己的缓存里。真正能做到“零网络访问直接渲染”的是Service Worker这一层。Service Worker的本质是一个独立于页面线程的代理。浏览器在注册它之后所有页面的网络请求都会先经过SWSW可以决定直接返回缓存、走网络、或者网络失败时回退缓存。在这个机制上Google的Workbox把缓存逻辑封装成了一组开箱即用的方案。最基础的模式叫precache。构建阶段Workbox会生成一个资源清单manifest把当前版本所有静态资源的URL和hash记录下来并在SW安装时把这些资源全部缓存到Cache Storage里。这样用户首次打开页面后第二次访问时SW能直接命中缓存、返回App Shell页面在完全没有网络请求的情况下完成基本渲染。我做过的博客站就是用这个思路HTML、CSS、JS全部precache用户再次访问时从SW直接取页面响应时间在弱网环境下也能做到两三百毫秒。注意precache的“首次缓存”虽然会多占用一些存储空间但它带来了完全可控的离线能力和秒开体验是一笔划算的买卖。Workbox里配置precache非常简洁import { precacheAndRoute } from workbox-precaching; precacheAndRoute(self.__WB_MANIFEST);self.__WB_MANIFEST是构建时由Workbox插件自动生成的列表。作为架构师你更需要关注的是这个列表的生成时机和粒度——不能把用户生成内容、动态图片也塞进去那不是precache的对象。4.2 运行时缓存策略SWR 的妙用和坑除了precacheSW还可以对动态请求做运行时缓存。常见策略有三种我按推荐程度排一下StaleWhileRevalidateSWR先返回缓存再在后台发请求更新缓存。适合图片、列表类数据用户永远看到的是上一次的内容但下一次就会变新。CacheFirst先查缓存有就直接返回没有才请求网络。适合头像、字体、不常变的图片。NetworkFirst先请求网络网络失败才用缓存。适合对新鲜度有要求但又想兜底的场景比如文章详情、首页。实际项目里我把SWR用在了文章列表上第一遍请求后缓存后续用户反复进入列表页时瞬间返回同时后台刷新缓存。这个策略让列表页第二次打开几乎没有任何等待时间。但SWR有个隐蔽的坑如果后端接口返回的数据在缓存期间被更新用户看到的可能是“上一次”的旧数据。所以在做文章详情这类对正确性有要求的场景时我通常改成NetworkFirst并且设置较短的过期时间比如60秒避免用户一直看着旧内容。还有一点SW里的缓存空间不是无限的。浏览器对Cache Storage有一定配额限制如果大量缓存大图片、视频流配额满了之后新增缓存会失败甚至可能把旧缓存意外驱逐。所以运行时缓存的注册条件要写清楚只拦截request.destination image这类明确资源类型的请求而不要整个页面所有请求都塞进SW。4.3 缓存的清理与更新别让SW变成磁盘垃圾生产者SW的坑往往出现在版本更新上。很多人上线第二版时会发现用户还在用第一版的SW和第一版的缓存页面怎么刷新都“不对”。这里要理解SW的生命周期浏览器发现SW脚本变了之后会安装新版本但新版本会进入waiting状态直到旧版本控制的页面全部关闭后才会接管。如果不加处理用户不关掉所有标签页新SW就一直等。在Workbox里我习惯手动开启skipWaiting和clientsClaimskipWaiting(); clientsClaim();skipWaiting让新SW安装后立即激活clientsClaim让新SW立即接管当前已打开的页面。再配合precache机制的版本管理——每次构建生成的precache清单都不同Workbox会自动清理旧缓存中已经不存在的资源。这样“发版后用户还挂在旧版”的问题基本就解决了。我踩过的一个真实教训是某个内部工具页面SW里缓存了老版本的路由配置发版后我忘了更新SW版本号结果所有用户连续三天打开的都是老界面。最后排查发现SW脚本本身被浏览器强缓存了max-age86400浏览器根本没去检查新SW。所以SW脚本本身必须用Cache-Control: no-cache来响应绝对不能进强缓存。这条我现在写进团队规范里了。5. 缓存失效与一致性问题架构师绕不开的硬骨头5.1 版本化资源与HTML入口最安全的缓存失效方式缓存设计里“怎么失效”比“怎么缓存”更重要。失效做得不好再快的缓存都是给自己埋雷。业界公认最稳妥的失效方式是“版本化资源 短缓存HTML”。逻辑是这样的所有静态资源经过构建都会生成内容hash文件名main.abc123.js、main.def456.js是两个完全不同的URL。需要更新时我们只需要让HTML入口文件及时指向新的URL浏览器就会去下载新资源而老资源的缓存不影响任何东西。然后HTML入口的响应头必须设置为Cache-Control: no-cache让浏览器每次访问都到服务器验证HTML内容。HTML本身很小不会有性能负担但它承担着“资源索引”的职责必须保持最新。这套组合拳做完你会发现“缓存清不掉”的痛感几乎消失。用户不需要强制刷新CDN不需要手动purge所有文件。真正需要purge的只有HTML这一层而它由于设了no-cache回源天然能得到最新版本。发布顺序也需要控制在部署时先上传带新hash的静态文件再更新HTML引用。否则如果先更新HTML用户请求到新的文件名但CDN上还没有就会出现502或短暂的404。这个顺序问题我在K8s部署和对象存储直出的项目中都遇到过多次值得写进发布checklist。5.2 前端数据缓存的一致性与后端失效联动数据缓存的前端一致性并不只是“后端删了缓存前端不知道”。更多场景是前端为了体验缓存了接口数据或本地数据但后端数据其实已经变了。我的处理原则是前端缓存的数据要么是天然弱一致的要么必须有明确的失效信号。天然弱一致的数据典型是“浏览历史、商品推荐流”用户不关心它是否绝对最新这类直接定时失效即可。而有强一致要求的数据比如“登录状态、优惠券数量、订单状态”前端就不应该做任何缓存如果某些模块为了流畅度必须缓存也要在关键操作下单、支付、领取优惠券发生后主动清除对应缓存key。更高级的联动方案是“版本号订阅”后端在更新关键业务数据时写一个数据版本号前端有缓存的页面轮询这个版本号的接口一旦发现版本号变了就主动拉新数据并刷新本地缓存。这种方式比“每隔30秒轮询业务数据”的流量消耗小得多是架构师做前后端失效联动时常用的一招。我经历过一个教训社区动态Feed页做了SW缓存用户体验极好但运营后台改了一条置顶内容后前端所有用户仍然看到旧置顶整整持续了一整天。后来我加了“内容版本号”机制版本号一变化立即触发SW缓存更新并把comms策略改成NetworkFirst问题才真正根治。5.3 穿透、击穿、雪崩前端侧的观察与降级表现缓存治理这个词在后端体系里讨论得多比如Redis缓存、MyBatis的二级缓存、Spring的三级缓存原理各有侧重但落到全局架构上它们和前端缓存是同一套思路的延伸把热点数据放在离用户更近的地方并设计好失效后的重建流程。后端的缓存穿透、击穿、雪崩前端其实能嗅到信号缓存穿透请求一个不可能存在的key每次都打穿缓存到数据库。前端表现是某些接口经常超时、返回空数据。缓存击穿某一个热点key过期瞬间涌入大量请求全部打到数据库。前端表现是某个热门页面突然变慢峰值过后又恢复。缓存雪崩大量key同时过期服务端压力瞬时暴增。前端表现是全站接口普遍变慢、请求大面积超时。架构师在前端侧能做的是观察与降级接口全局加超时和重试控制不能因为服务端抖动导致前端页面直接崩掉热点接口的请求要尽量合并同一时间多个组件重复请求同一个数据源时用一个共享的Promise把请求合并成一次服务端不可用时SW可以临时返回最后一次成功的缓存数据并附带“内容可能不是最新”的标记。后端侧的治理比如Redis缓存的热点key过期时间加随机抖动、布隆过滤器防穿透、分布式锁防击穿虽然不在前端代码的范畴但架构师必须知道它的存在和边界。因为你在做前端优化时设计出来的缓存策略要和后端团队讨论能配合到什么程度。前端说“我缓存了30秒”后端就必须承接这30秒内可能产生的数据不一致责任。这个“职责边界”是架构分层中最容易撕逼、也最能体现架构师统筹能力的地方。6. 实战排查缓存问题定位与避坑心得6.1 缓存不生效先按这个顺序排查缓存不生效是排查频率最高的问题。我建议你按下面这个顺序来不要一上来就怀疑代码打开DevTools的Network面板点中某个请求查看Response Headers。第一个问题永远是服务器到底有没有返回Cache-Control、ETag这些字段没有就谈不上缓存生效。看URL。不要只看文件名要看整个URL有没有带随机的query参数尤其是时间戳。?t123456这种写法会让同一个资源被当成不同URL缓存必然不生效。看请求来源。Network面板里Size列如果显示from disk cache或from memory cache说明浏览器缓存生效如果显示from ServiceWorker说明是SW在响应如果每次都显示不透明的网络流量说明缓存链路断了。看SW是否劫持了请求。去Application面板里的Service Workers查看当前页面被哪个SW控制有没有生效是否处于activated状态。SW脚本如果被强缓存更新也会失败。如果是线上问题再确认CDN。随便找一台边缘节点机器用curl发起一次请求带上相同的请求头看响应里的s-maxage字段和节点缓存状态。CDN配置的忽略参数、缓存key规则都可能影响命中。一条容易忽略的常识浏览器在地址栏输入URL回车时会携带重新验证的意图某些资源即使没过期也可能重新走协商缓存。所以手动刷新页面时看到的网络表现和用户正常点击链接进入页面时的表现是两回事。排查时一定要复现“用户正常进入”的流程而不是自己F5猛刷。6.2 自动化测试与验收时缓存最会“骗人”这个坑我相信做质量保障的同事都懂自动化测试里最常见的“偶发性失败”很大一部分来自缓存。举个例子你用Selenium或Playwright跑一条用例第一步打开页面断言某个元素存在第二步重新打开页面断言另一个元素。由于浏览器复用了缓存第二次打开时可能直接用了旧版HTML或旧版JS导致断言结果和你本地手工验证的不一致。如果测试框架再默认走一个带缓存profile的浏览器实例这个问题会更难发现。我目前的处理习惯是把自动化测试环境的浏览器实例设成“禁用缓存”的profile或者在请求级别统一加一个随机参数确保每个用例看到的都是新鲜资源。这不是性能上的最优解但测试的目的是验证功能正确性不是验证缓存策略。缓存策略本身的验证应该单独写一套针对响应头的断言而不是混在业务用例里。验收线上版本时也是一样如果业务同学反馈“更新后还是旧页面”第一句话不要急着说修先问“你刷新了几次”“是不是从微信里打开的”“有没有用无痕窗口试过”。很多时候是浏览器或微信内置浏览器的缓存没清产品经理自己点了一个收藏夹里的历史URL里面还是旧HTML引用。修好这个问题最快的方式不是代码而是流程上给业务同学一个“强制刷新”的操作指引。6.3 浏览器缓存目录与本地存储的清理问题最后说一个架构师可能遇到的“杂活”Chrome和Edge的用户数据目录越来越大。这个问题登上热搜的版本是“windows传递优化缓存占了很多内存”“edge浏览器缓存位置改到d盘”本质上都是浏览器缓存和应用缓存长期不清理导致的磁盘占用。站在网站开发者的角度我们能约束的是自己网站产生的缓存Service Worker的Cache Storage、IndexedDB、LocalStorage。特别是SW的precache如果版本管理不到位旧版本的缓存可能长期躺在用户的Cache Storage里不被清理日积月累就是几十MB到几百MB的膨胀。我处理这个问题的原则是在SW的activate事件里主动清理旧缓存只保留当前版本需要的缓存。Workbox的precache模块本身会做这件事但如果你自己手写了SW逻辑千万别漏掉这一步。另外很多站点把大体积资源图片、视频一股脑塞进SW缓存却忘了设置过期策略这也会让用户的浏览器目录迅速膨胀。对于这类资源我会给运行时缓存配置maximumEntries或者expiration参数限制缓存数量和时间。用户个人电脑上清理浏览器缓存目录只需要在Chrome/Edge设置里找到“清除浏览数据”或者直接用扩展到D盘的方式目录迁移但这些操作和网站性能优化是两码事。架构师要关注的是保证自己站点的缓存策略不成为用户磁盘空间的负担这属于体验设计的一部分。在我个人的实践里最顺手的组合是开发阶段完全不碰缓存用no-store保证每次改动都能立刻看到测试阶段开启强缓存专门验证“二次访问”的表现生产阶段全量放开前置资源和缓存策略但保留一条“无缓存测试白名单”URL供技术同学随时回归原始加载链路。这条白名单URL看起来不起眼但它让我在排查缓存问题时节省了大量时间——当我不确定问题是不是缓存导致的就直接走白名单URL立即还原真相。这个习惯我建议每个带前端团队的架构师都养一个。前置资源和缓存这件事做深了你会发现它不是一个“配置项”而是一套贯穿发布流程、前后端协作、质量保障的系统设计。真正把这几层做好了你的页面在首次访问时有前置资源在抢时间二次访问时有HTTP缓存和SW在扛流量版本更新时有版本号和失效机制在兜底这套组合带来的稳定收益远比追一个新框架实在得多。
返回列表