你的网站为什么加载慢?
打开一个网页,F12看Network面板。你会发现前3秒基本都在等资源下载。
CSS文件50KB,JS文件200KB,图片加起来超过2MB。如果这些资源每次都要重新下载,你的网站能不慢吗?
HTTP缓存是解决这个问题的核心机制。配得好,页面第二次加载只需几百毫秒。配不好,每次都是完整的网络请求。
HTTP缓存的基本原理
浏览器缓存分两层:
强缓存(不走网络请求):浏览器根据Cache-Control头部决定是否直接使用本地副本。命中强缓存时,Network面板显示(memory cache)或(disk cache),状态码为200但不会发送HTTP请求。
协商缓存(发请求但可能不传数据):浏览器带上ETag或Last-Modified头向服务器验证资源是否变化。服务器返回304表示"没变,你用本地的",返回200表示"更新了,给你新的"。
两者的关系是:先检查强缓存,不命中就走协商缓存,协商缓存也不命中才真正下载。
Cache-Control详解
这是最常用的缓存控制头。它的值决定了缓存的行为:
Cache-Control: max-age=31536000, immutable
max-age=31536000表示资源在浏览器中可以缓存一年(31536000秒)。immutable告诉浏览器这一年之内资源永远不会变,连协商缓存都不用发。
常见组合:
| 场景 | Cache-Control值 | 说明 |
|---|---|---|
| 静态资源(CSS/JS/图片) | public, max-age=31536000, immutable | 长期缓存,文件名带hash时最安全 |
| HTML页面 | no-cache | 不走强缓存,每次都要协商验证 |
| 动态API | no-store | 完全不缓存,每次从服务器拿 |
| 半静态数据 | max-age=60, s-maxage=3600 | 浏览器缓存1分钟,CDN缓存1小时 |
关键点:max-age只对浏览器生效。如果你想让CDN也参与缓存,需要用s-maxage覆盖。
文件名Hash是缓存的灵魂
很多人配了max-age=31536000,然后发现改了CSS之后用户看不到新样式。原因很简单:浏览器还在用旧的缓存文件。
解决方案是给文件名加hash:
style.a3f5b8.css ← 内容变了,hash就变了
script.9c2d1e.js
image.logo.png ← 图片用时间戳或版本号
当内容变化时,文件名也跟着变。浏览器认为这是一个全新的资源,直接下载。旧文件因为没人引用,最终会被浏览器清理掉。
Vite、Webpack这些现代构建工具默认就做了这件事。如果你的项目还没加hash,花10分钟配一下,效果立竿见影。
ETag:精确验证的利器
协商缓存有两种方式:Last-Modified和ETag。
Last-Modified基于时间戳,精度只有1秒。如果文件在1秒内被修改了两次,第一次修改不会被检测到。
ETag基于内容的哈希值。服务器给每个资源生成一个唯一标识符,通常是文件的MD5或SHA1:
ETag: "a3f5b8c9d0e1f2"
浏览器再次请求时带上这个值:
If-None-Match: "a3f5b8c9d0e1f2"
服务器比对后返回304(未修改)或200(已更新,附带新的ETag)。
ETag更精确,但计算成本更高。对于小文件来说差别不大,但对于大体积的响应体,建议用Last-Modified节省服务器CPU。
Nginx缓存配置实战
实际项目中,Nginx是最常见的反向代理服务器。以下是生产环境的缓存配置模板:
server {
listen 80;
server_name example.com;
# 静态资源:长期缓存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
root /var/www/html;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# HTML页面:协商缓存
location ~* \.html$ {
root /var/www/html;
add_header Cache-Control "no-cache, must-revalidate";
add_header Pragma "no-cache";
}
# API接口:不缓存
location /api/ {
proxy_pass http://backend:3000;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# 媒体文件:短期缓存
location ~* \.(mp4|webm|ogg)$ {
root /var/www/html;
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
}
几个要点:
- 静态资源用
expires 1y加immutable,配合文件名hash使用 - HTML用
no-cache而不是no-store,因为HTML本身很小,协商缓存的成本可以接受 - API用
no-store彻底禁止缓存 - 媒体文件给一个合理的短期缓存时间
CDN缓存配置
CDN的缓存逻辑和本地缓存类似,但层级更多:
用户 → CDN边缘节点 → 源站
CDN缓存的关键参数是TTL(Time To Live)。源站返回的Cache-Control头会被CDN读取,作为缓存时长的依据。
Cloudflare的配置示例:
- 进入Caching → Configuration
- 设置默认TTL为1个月
- 添加规则:路径包含
/api/的请求TTL设为0(不缓存) - 添加规则:路径以
.js或.css结尾的TTL设为1年
更高级的做法是用CDN的"缓存键"功能。默认情况下CDN只按URL做缓存。如果你的页面有动态参数(比如?token=xxx),每个不同的token都会生成一个新的缓存条目,浪费CDN空间。
设置缓存键为只包含域名和路径,忽略查询参数,就能解决这个问题。
Service Worker:客户端级缓存
对于需要离线使用的场景(比如PWA应用),HTTP缓存不够用了。Service Worker能在浏览器层面接管网络请求,实现更精细的控制。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 先查缓存
if (cachedResponse) {
// 后台更新缓存
fetch(event.request).then(networkResponse => {
if (networkResponse) {
caches.open('v1').then(cache => {
cache.put(event.request, networkResponse);
});
}
});
return cachedResponse;
}
// 缓存没命中,走网络
return fetch(event.request);
})
);
});
这个策略叫"缓存优先,后台更新"。用户请求资源时先用缓存,同时后台悄悄拉取最新版本更新缓存。体验上是最快的,数据是最新的。
Service Worker适合SPA应用。对于传统多页网站,HTTP缓存+CDN的组合已经足够好,不需要引入额外的复杂度。
常见缓存陷阱
陷阱一:改了配置但浏览器还在用旧缓存
Chrome DevTools的Network面板有个"Disable cache"复选框。勾选后,只要DevTools开着就不会使用缓存。很多人以为自己的缓存配置无效,其实只是被DevTools欺骗了。关掉这个选项再测。
陷阱二:CDN缓存了不该缓存的内容
动态API返回了max-age=3600,CDN就把结果缓存了一小时。一小时内的所有请求都拿到旧数据。解决方法是在API响应头中加private而不是public,CDN就不会缓存了。
陷阱三:大文件缓存策略不当
视频文件动辄几百MB。如果设置immutable一年,CDN边缘节点会永久存储这些大文件,占用大量带宽和存储。视频文件应该用短TTL(比如1天)加上Range请求支持,让用户断点续传。
用工具验证你的缓存配置
配完缓存之后,怎么确认生效了?
方法一:打开浏览器开发者工具,Network面板勾选Preserve log,刷新页面。看资源的状态码:
- 显示
(memory cache)或(disk cache)且无HTTP请求 → 强缓存命中 - 显示
304 Not Modified→ 协商缓存命中 - 显示
200 OK且Size显示from disk cache→ 首次加载后的磁盘缓存
方法二:用命令行测试响应头:
curl -I https://example.com/style.css
看返回的Cache-Control和ETag是否符合预期。
方法三:用Lighthouse做全链路性能审计。它会告诉你哪些资源可以被缓存,哪些应该用CDN。
总结
HTTP缓存不是配一次就完事的。它需要根据你的资源类型、更新频率和用户场景反复调整。
先从最简单的开始:给静态资源加max-age=31536000, immutable,给API加no-store。然后观察Lighthouse的评分变化。你会发现,同样的服务器带宽,缓存配好之后能扛住3倍的流量。
你的网站现在用的什么缓存策略?有没有踩过缓存的坑?评论区聊聊。