前端页面性能优化(二)
前端页面性能优化之静态资源与缓存策略
从整个页面的视角来看,影响性能的远不止图片。CSS、JavaScript、字体文件、图标资源、接口响应等,都会直接决定用户能多快看到“可用的页面”和“可交互的页面”。
本文我们重点聚焦在:静态资源与缓存策略。
一、静态资源在性能中的角色
从浏览器的视角,静态资源大致可以分为:
- HTML 文档:通常不缓存或短缓存,承载结构与入口。
- CSS 样式:决定首屏布局与样式,是关键渲染路径的一部分。
- JavaScript 脚本:决定交互与业务逻辑,可能会阻塞渲染。
- 字体文件:影响文字渲染体验,体积也可能不小。
- 图片与图标:上篇已经重点讲过,这里只作为整体的一部分。
用户能否快速看到首屏,核心在于:
- 关键 CSS / JS 能否尽快拿到
- 非关键资源能否延后 / 按需加载
- 重复访问时能否直接命中缓存
这就引出了本文的主角:缓存策略 + 静态资源管理。
二、HTTP 缓存基础:强缓存与协商缓存
浏览器缓存机制大致分为两类:
- 强缓存(Freshness / Expiration):在有效期内,直接从本地缓存读取,不发请求。
- 协商缓存(Validation):向服务器发一个轻量请求,询问资源是否有更新。
理解这两者,是设计缓存策略的基础。
1. 强缓存:Cache-Control / Expires
典型配置:
1 | |
- max-age=31536000:资源在 1 年内视为“新鲜”,浏览器可直接使用。
- public:允许中间代理(比如 CDN)缓存。
老一点的写法是 Expires:
1 | |
但在现代实践中,推荐以 Cache-Control 为主。
2. 协商缓存:ETag / Last-Modified
当资源不适合长期强缓存(比如接口返回、频繁变动的文件)时,可以使用协商缓存。
Last-Modified / If-Modified-Since
- 服务器在响应头里带上
Last-Modified。 - 浏览器下次请求时带上
If-Modified-Since。 - 若资源未变化,服务器返回
304 Not Modified,不再重复传输内容。
- 服务器在响应头里带上
ETag / If-None-Match
- 服务器为资源生成一个唯一标识(可以是文件指纹或哈希)。
- 浏览器下次请求时通过
If-None-Match携带。 - 服务器对比后返回 304 或 200。
实践建议:
- 静态构建出的资源(带指纹的
.js、.css等)→ 优先用强缓存。 - 动态资源、接口返回 → 合理使用协商缓存,避免重复传输。
三、文件指纹与“缓存友好”的发布策略
几乎所有现代前端工程化方案(Webpack / Vite / Rollup 等),都会默认为构建产物添加“文件指纹”:
1 | |
1. 为什么需要文件指纹?
我们希望浏览器对静态资源长期强缓存,但又要保证代码更新时用户能拿到新版本。文件指纹正是用来解决这个矛盾:
- 内容不变 → 文件名不变 → 强缓存命中。
- 内容变更 → 指纹变化 → 文件名变化 → 浏览器视为新资源重新拉取。
因此一个经典的实践是:
1 | |
immutable 告诉浏览器:在 max-age 期间,这个文件不会变,不必发送条件请求,进一步减少开销。
2. 如何让 HTML 始终拉到“最新资源”?
通常发布策略是:
- HTML 不带强缓存或设置较短的
max-age。 - HTML 中引用的 JS / CSS 带有指纹,并被设置为一年以上的强缓存。
例如:
1 | |
解释:
- HTML 的
no-cache表示每次使用前需要向服务器确认是否有新版本(可能返回 304,也可能返回 200)。 - 静态资源一旦下发,就可以放心长期缓存。
3. SPA / 多页应用中的差异
SPA:
- HTML 通常只有一个入口。
- 强烈建议设置短缓存 +
no-cache,以确保路由和入口脚本引用更新。
多页应用:
- 可以按路由拆分 HTML。
- 常见做法依然是“HTML 短缓存 + 资源长缓存”。
四、CDN 与就近访问:把资源“搬到用户旁边”
在图片优化那一篇中已经提到,CDN 对于图片非常重要。对 JS / CSS / 字体 / 接口等来说,同样如此。
1. 为什么要用 CDN?
- 物理距离更近:减少 RTT(往返时间)。
- 多节点分担流量:缓解源站压力,提升并发能力。
- 协议支持更好:很多 CDN 默认支持 HTTP/2 / HTTP/3、TLS 优化等。
2. 常见的静态资源部署结构
- 前端静态资源托管到 CDN 域名:
https://static.xxx.com/app.23fd8a9c.jshttps://static.xxx.com/index.41d2e8e1.css
- 源站一般是对象存储或静态服务器:
- 如 OSS / COS / S3 / Nginx 等。
3. CDN 配置中的关键点
开启缓存与压缩
- 启用 Gzip / Brotli 压缩。
- 遵循源站的
Cache-Control,或在 CDN 配规则覆盖。
正确处理回源与版本
- 使用带指纹的路径,避免缓存“脏数据”。
- 更新版本时,可以只上传新文件,由于文件名变化,CDN 会自动回源。
利用 CDN 的“格式自适应”能力
- 类似上一篇讲的
?format=auto,对于图片尤为常见。 - 有些 CDN 对静态文本资源也提供自动压缩、合并等优化能力(视产品而定)。
- 类似上一篇讲的
五、预加载 / 预获取:让关键资源更早到达
缓存解决的是“减少重复下载”,而预加载 / 预获取则是“把本来会晚一点下载的东西,提前一点拿到”。
1. preload:提前加载当前页面“必需”资源
<link rel="preload"> 可以让浏览器在更早的阶段加载特定资源,例如:
1 | |
使用要点:
- 只对关键且确定会使用的资源使用。
- 正确设置
as属性,有利于优先级调度和安全策略。
2. prefetch:为“下一步的页面”提前准备资源
<link rel="prefetch"> 更适合未来可能用到的资源,比如下一页的 JS 包:
1 | |
浏览器会在空闲带宽时下载这类资源,不会阻塞当前页面的关键加载。
典型场景:
- 单页应用中,对“高概率访问”的路由提前 prefetch。
- 用户停留在首页较久时,预取下一步引导页的资源。
3. dns-prefetch / preconnect:减少网络握手开销
如果你使用了多个域名(如 API 域名、CDN 域名、第三方 SDK 域名等),可以通过:
1 | |
适用场景:
- 页面上一定会访问的外部域名,且连接本身耗时明显。
六、字体资源优化:看得见的“加载闪烁”
字体文件(ttf / woff / woff2 等)在现代页面中越来越常见,尤其是定制化 UI、图标字体等。但字体加载不好,很容易出现:
- 白板无字(FOIT:Flash of Invisible Text)
- 字体闪烁(FOUT:Flash of Unstyled Text)
1. 使用 font-display 控制渲染策略
在 @font-face 中增加 font-display,例如:
1 | |
常用取值:
swap:优先用系统字体渲染,字体加载完成后再切换。fallback:如果在一小段时间内未加载完成,就一直用系统字体。
这两种策略可以有效避免长时间“无字可见”的情况。
2. 字体子集化:只保留需要的字符
很多时候我们只需要一小部分字符(例如中文 + 英文 + 数字 + 常用标点),可以通过子集化工具裁剪字体集:
- 例如
fonttools、pyftsubset、在线子集化服务等。 - 结合工程构建,在 CI 流程中自动生成子集字体。
子集化后:
- 文件体积显著减小。
- 加载时间缩短,首次渲染更快。
3. 优先使用系统字体 / 本地字体
对性能要求较高的业务(如后台管理、工具类产品),完全可以:
- 优先使用系统字体,避免额外字体请求。
- 或通过
local()优先命中本地已有字体。
示例:
1 | |
七、静态资源压缩与打包:更小、更少、更快
在图片格式优化的基础上,其他静态资源同样需要压缩与按需加载。
1. 文本压缩:Gzip / Brotli
确保服务端或 CDN 开启对以下类型资源的压缩:
text/htmltext/cssapplication/javascriptapplication/jsonimage/svg+xml
现代 CDN/服务端一般都支持 Gzip 和 Brotli:
- Brotli 在大多数场景下压缩率更优,推荐优先启用。
- 浏览器会根据
Accept-Encoding决定使用哪种压缩。
2. JS / CSS 按需拆分与 Tree Shaking
构建时应尽量做到:
- 只打包实际用到的代码(Tree Shaking)。
- 根据路由 / 功能模块进行代码拆分(Code Splitting)。
- 公共依赖抽离成 vendor 包,便于缓存。
示例(以 Webpack 为例,简化说明):
1 | |
配合合理的命名策略,可以在构建时生成:
home.aa11bb22.jsabout.3344cc55.js
配合缓存策略,实现“页面拆分 + 长缓存”。
八、实战中的资源与缓存策略搭配方案
综合上面的内容,可以给出一套比较实用的“默认方案”(可根据业务再细化调整)。
1. 静态构建产物
- 文件命名:带内容指纹,如
app.[hash].js、index.[hash].css。 - HTTP 头:
1 | |
2. HTML 文档
- 不加指纹,路径稳定(如
/index.html)。 - HTTP 头:
1 | |
3. 图片资源
- 根据上一篇的建议,采用 WebP / AVIF + JPG/PNG 兜底。
- 静态图片同样使用指纹 + 长缓存。
4. 字体资源
- 尽量做子集化。
- 采用
font-display: swap或fallback。 - 静态字体文件可以使用长缓存。
5. 接口 / 动态资源
- 对 GET 请求使用合理的协商缓存(ETag / Last-Modified)。
- 对 POST、灵活接口,结合业务决定是否缓存或禁用缓存。
九、总结
在前端性能优化的体系中,静态资源与缓存策略是和“图片优化”同等重要的基础能力:
- 通过 文件指纹 + 长缓存,让浏览器把不会变的资源牢牢记住。
- 通过 HTML 短缓存 + 缓存验证,确保更新能及时到达用户。
- 通过 CDN + 压缩 + 预加载 / 预获取,让关键资源更快、更近地送达。
- 通过 字体与文本资源优化,减少“肉眼可见”的加载闪烁与卡顿。
只有把这些基础做好了,用户才能在第一时间看到一个“可用的页面”,为后续的交互流畅打下坚实的基础。