一、引言:缓存是前端性能的核心
在现代 Web 应用中,缓存几乎是影响前端性能的最关键因素。从用户体验出发,页面加载速度直接决定了用户的停留时长与转化率。研究显示,页面每延迟一秒,转化率就可能显著下降。而像 Lighthouse 评分、核心 Web 指标(Core Web Vitals:LCP、FID、CLS)等性能评估体系,也将"首屏渲染速度""交互延迟"等指标作为衡量体验的核心,这些都与缓存策略密切相关。
缓存的价值不止于用户体验,它还带来多维度的收益:
- 减少网络请求:命中缓存时,资源直接来自本地或中间层,不必每次都访问服务器。
- 节省用户带宽:移动端用户尤其受益,重复访问时不会再消耗额外流量。
- 减轻服务器压力:海量请求若能通过缓存拦截,后端就能专注处理真正的动态业务逻辑。
在这个体系中,前端并不是单纯的缓存消费者。借助构建工具(如 Webpack、Vite)、资源命名策略(如文件指纹 hash)、以及与服务器(Nginx、CDN)的协同配置,前端工程师往往是缓存策略的设计者与执行者。正是这种"前端定义缓存、全链路受益"的模式,让缓存成为前端性能优化的核心议题。
二、Http缓存机制详解
HTTP缓存时Web性能优化中最重要的环节之一,其核心目的就是尽可能的减少客户端向服务器发起请求,同时保证资源的更新和正确性。
HTTP 缓存机制大体可以分为两类:强缓存 和 协商缓存。
强缓存
强缓存命中时,浏览器不会与服务器发生任何请求,直接从本地缓存中读取资源,速度最快。
关键字:有expires
和Cache-Control
-
expires
时HTTP/1.0中的过期时间,指定一个绝对时间点,yamlExpires: Wed, 21 Oct 2025 07:28:00 GMT
缺点就是依赖客户端时间,如果客户端时间不准,可能会失效
-
Cache-Control: max-age=seconds
:HTTP/1.1 推荐用法,指定资源在多少秒内有效。iniCache-Control: max-age=31536000
表示一年内缓存有效
强缓存可以通过
Network
面板中看请求,一般都是 在"Size"列会显示from memory cache
(内存缓存) 或from disk cache
(磁盘缓存),并且Status
列是200
。
协商缓存
当强缓存过期后,浏览器会向服务器验证缓存是否任然有效。如果服务器告诉它还有效,就继续使用本地缓存,否则就返回新资源。
关键字有:Last-Modified / If-Modified-Since
和ETag / If-None-Match
-
Last-Modified / If-Modified-Since
是服务器加在响应头上,yamlLast-Modified: Wed, 21 Oct 2025 07:28:00 GMT
下次请求时,浏览器带上:
yamlIf-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
如果文件没有发生变化, 服务器返回
304 Not Modified
,不再传输实体内容。 -
ETag / If-None-Match
,ETag
是资源的唯一标识,vbnetETag: "abc123"
下次请求时,浏览器带上:
sqlIf-None-Match: "abc123"
标识一致返回304,否则就返回新资源。
ETag
优先级高于Last-Modified
缓存的行为由服务器响应头和浏览器策略共同决定。常见的 Cache-Control
组合有:
Cache-Control: no-store
→ 不缓存,敏感数据场景。Cache-Control: no-cache
→ 缓存但每次都要协商验证。Cache-Control: public
→ 可以被任何中间层缓存(如 CDN)。Cache-Control: private
→ 只能被浏览器缓存,CDN 不能缓存。Cache-Control: max-age=0, must-revalidate
→ 马上过期,但必须重新验证。
一般设置缓存策略都是在nginx中进行配置
比如设置强缓存:
bash
location /static/ {
# 设置强缓存:1年内有效
add_header Cache-Control "public, max-age=31536000"; # 31536000秒=1年
expires 1y; # 等效于Expires头,1年后过期
}
首次请求直接返回资源及缓存头,后续请求浏览器直接读取本地缓存,状态码 200 (from disk cache)
或200 (from memory cache)
,不会发送请求到服务器。
设置协商缓存
csharp
location /dynamic/ {
# 启用协商缓存(默认已支持,无需显式配置ETag)
add_header Last-Modified ""; # 可选:覆盖默认的Last-Modified
etag on; # 启用ETag(默认开启)
}
首次请求服务器返回资源及 Last-Modified
或ETag
,后续请求浏览器携带 If-Modified-Since
或If-None-Match
请求头验证缓存 ,若资源未修改,服务器返回 304 Not Modified
,浏览器继续使用缓存;若资源已修改,服务器返回新的资源
三、构建工具固化缓存策略
当在nginx设置强缓存,静态资源的有效期为一年时,期间如果要更新这些静态资源,应该怎么做呢,比较古老的做法就是手动在静态资源后面打上一个时间戳,每次发版都手动更新下,当url发生了变化,自然就会重新从服务端重新获取数据,但是这种做法还是非常的繁琐的,有了web构建工具,这些就变得非常的简单了
比如在webpack
中:
ini
// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js', // 自动启用强缓存
},
};
在vite中
arduino
// vite.config.js
export default {
build: {
assetsDir: 'static',
},
};
然后入口文件index.html
通常是不带hash值的,通常设置为Cache-Control: no-cache 或 max-age=0
,直接启用协商缓存,由服务端来判断入口文件是否发生了变更,如果更改了直接返回最新的,没有更改,就返回304。
四、理解nginx缓存
首先了解下nginx
Nginx(发音为 "engine-x")是一款高性能、开源的 Web 服务器 、反向代理服务器 、负载均衡器 和 HTTP 缓存。它因其稳定性、丰富的功能集、简单的配置和极低的资源消耗而闻名,如今已成为全球最受欢迎的 Web 服务器之一。
正向代理与反向代理
-
正向代理:
正向代理是一个位于客户端和目标服务器之间的服务器。客户端发送请求时,请求先到达正向代理服务器,然后由正向代理服务器转发到目标服务器。目标服务器看到的是来自代理服务器的请求,而不是直接来自客户端。
经常使用nginx来正向代理前端请求处理跨域问题。
一般科学上网、内网访问外网、公司内网统一出口,都是正向代理的典型应用场景。
-
反向代理
反向代理是 服务器端的代理。客户端访问的目标就是代理服务器(比如 Nginx),由它把请求转发给后端真实服务器,再把结果返回客户端。
常见的web利用nginx代理跨域就是反向代理的经典使用场景。
写这篇博客的起源就是,在业务开发过程中,遇到了这样一个场景:接口采用的是stream流式输出,后来遇到了这样一个问题:在接口传输过程中,后端通过日志发现数据已经发送了,但是前端解析时不同步,前端接受到的数据总是延后,后来就发现了这个东西,nginx缓冲。
接下来学习下nginx缓存和缓冲。
缓存
缓存的核心目的是减少重复计算和重复请求,将响应保存下来,以便在后续相同的请求中直接使用,从而提升服务器性能,
一般来说nginx主要有两类缓存,静态资源缓存和代理缓存
静态资源就是和前面的强缓存一样,把静态资源缓存到内存或者磁盘;而代理缓存就是当nginx作为反向代理时,可以缓存上游后端的响应,比如请求接口GET /api/data
,后端返回一端JSON
,nginx可以直接缓存,下一次请求就不用再走后端了,比如这样配置:
ini
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
server {
location /api/ {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m; # 成功和重定向缓存10分钟
proxy_cache_valid 404 1m; # 404缓存1分钟
}
}
静态资源缓存一般都是存放再浏览器本地,这是常规强缓存的存储方式,nginx自己并不会把静态文件缓存起来,而是直接从磁盘读取,它可以缓存文件句柄和元信息,加快打开文件的速度,但不是内容缓存。总结一下:强缓存是将文件存放再浏览器本地,协商缓存才会用到nginx缓存,nginx缓存可以加快读取打开文件的速度。
缓冲
缓冲是请求和响应过程中的临时存储,优化网络传输,主要分为两类
-
请求缓冲
Nginx 在收到客户端请求体(比如大文件上传)时,不会直接转发给后端,而是先放到本地缓冲区。
控制指令:
client_body_buffer_size
、client_max_body_size
。 如果缓冲区不够大,多余的部分回写道临时文件中 -
响应缓冲
当 Nginx 从上游(后端)拿到响应数据时,会先放进缓冲区,再根据情况返回给客户端。 后端响应慢, nginx 可以先吞下,客户端不用直接受影响。避免边收边转发,提升性能。
这个缓冲主要是为了避免客户端长时间和服务端链接,占用后端的进程、线程,一般都是默认开启的,然后遇到了流式传输这种需要实时数据时,就需要手动关闭它
bash
location /live {
proxy_pass http://backend;
proxy_buffering off; # 关闭缓冲,实现实时流
}
五、一套完整的前端缓存策略
静态资源缓存策略
静态资源指JS、CSS、图片、字体等版本不变则内容也不变的文件。对它们的最佳实践是:开启强缓存,并通过内容哈希实现"精确更新" 。
- 在构建工具中配置内容哈希
这是实现强缓存的前提。使用Webpack、Vite、Rollup等工具打包时,必须为文件名添加哈希。
Webpack配置示例:
ini
// webpack.config.js
output: {
filename: '[name].[contenthash].js', // 使用内容哈希
chunkFilename: '[name].[contenthash].chunk.js',
assetModuleFilename: 'assets/[name].[contenthash][ext]',
},
- 效果 :文件内容任何微小变化都会生成一个全新的哈希值文件名,如
app.
a1b2c3
.js
->app.
d4e5f6
.js
。 - 为什么? 因为全新的URL意味着全新的请求,从而绕过任何旧的缓存。这样你就可以放心地把旧文件缓存非常久的时间。
- 在Web服务器上配置强缓存头
为这些带哈希的静态资源设置超长的 Cache-Control
头。
Nginx配置示例:
ini
server {
listen 80;
server_name yoursite.com;
# 匹配带哈希的静态资源
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
# 查找资源并设置缓存头
root /path/to/your/static/files;
# 设置强缓存:1年
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
}
# 其他配置,比如处理HTML...
}
public
:允许浏览器和CDN等代理缓存。max-age=31536000
:缓存有效期一年(以秒为单位)。immutable
:明确告诉浏览器,这个资源在有效期内永不会改变,期间用户刷新页面时浏览器也不会发起请求验证(If-None-Match
),极大提升刷新性能。
HTML文件的缓存策略
HTML文件通常是入口文件,它引用了带哈希的静态资源。如果HTML被强缓存了,用户就无法获取到新的资源链接。
策略:使用协商缓存或短暂的强缓存。
Nginx配置示例:
ini
server {
# ... 其他配置 ...
# 处理HTML文件
location / {
root /path/to/your/html/files;
index index.html;
# 方式1:使用协商缓存 (推荐)
add_header Cache-Control "no-cache";
# 方式2:设置一个非常短的强缓存时间 (例如5分钟)
# expires 5m;
# add_header Cache-Control "public, max-age=300";
}
}
no-cache
:不是不缓存 ,而是在使用缓存前必须向服务器验证 (发送带有If-None-Match
的请求)。如果服务器返回304,则直接使用本地缓存。这保证了用户总能拿到最新的HTML。- 短时间强缓存:对一些变化不频繁的官网首页,可以设置几分钟的强缓存,在性能和新鲜度之间取得平衡。
API响应的缓存策略
API响应通常是动态的、个性化的,需要非常谨慎地设置缓存。
-
绝对私人数据 :使用
Cache-Control: no-store
。不存储任何副本。nginx
bash# 在Nginx代理层或后端应用代码中设置 add_header Cache-Control "no-store";
-
可共享的公共数据:对于一些更新不频繁的公共API(如新闻列表、配置信息),可以设置短暂的缓存,显著降低服务器负载。
nginx
bash# 在Nginx中为特定API路径设置缓存 location /api/public/news { proxy_pass http://backend; # 缓存10分钟,且仅限代理服务器缓存 add_header Cache-Control "public, s-maxage=600"; }
s-maxage
:仅适用于代理缓存(如CDN、Nginx),浏览器会忽略它。这样可以在服务器层面加速,而不影响客户端获取最新数据。
-
用户相关的私人数据 :使用
private
。nginx
arduinoadd_header Cache-Control "private, max-age=3600";
private
:表示响应只适用于单个用户的浏览器缓存,中间的代理服务器(CDN)不能缓存它。
总结一下:
-
前端构建:
- 对所有静态资源(JS, CSS, 图片等)使用
[contenthash]
命名。 - 将静态资源与HTML文件分开部署(通常上传到CDN)。
- 对所有静态资源(JS, CSS, 图片等)使用
-
服务器配置:
- 带哈希的静态资源 :→
Cache-Control: public, max-age=31536000, immutable
(强缓存一年) - HTML文件 :→
Cache-Control: no-cache
(总是协商验证) - API接口 :根据敏感性选择 →
no-store
(不缓存)或private
(私有缓存)或public, s-maxage=600
(公共代理缓存)
- 带哈希的静态资源 :→
-
协作:
- 前端需要明确告知后端或运维不同资源所需的缓存策略。
- 使用CDN时,前端构建的哈希命名和后端配置的缓存头同样适用,有时还需在CDN管理后台进行额外配置和"缓存净化"操作。
这套组合拳是现代Web开发中缓存设置的黄金标准,完美兼顾了性能体验和更新需求