一个网站的页面打开慢,十有八九不是 HTML 本身慢,而是它背后拖着一大堆 CSS、JS、图片、字体。这些统称为静态资源,它们的共同特点是:内容对所有人一样、更新频率低、可以被长期缓存。正因为这几个特点,静态资源是性能优化里投入产出比最高的部分。
这篇文章讲怎么把静态资源加速做到位:从 Nginx 侧配置缓存和过期头、开启压缩、做动静分离,到资源文件名加版本指纹,再到 CDN 侧怎么配缓存规则。这些都是可以直接落地的操作,配好之后页面加载速度和源站带宽压力通常都有明显改善。
一、静态资源为什么值得优先优化
先搞清楚优化的优先级。一个页面的加载时间可以粗略拆成几块:HTML 文档的获取、静态资源的获取、脚本执行和渲染。其中 HTML 通常很小,真正占时间和流量的是静态资源。
静态资源又分两类开销:第一次访问时是"下载开销"(文件有多大就传多久),重复访问时是"重复下载开销"(如果没缓存,用户每次来都要重新下)。优化思路对应也就两条:让文件变小(压缩、图片格式优化),以及让文件不用重复下(缓存)。
还有一个容易被忽略的维度是"请求数"。老式网站在一个页面里塞几十个小文件,每个请求都要走一次网络往返,累积起来很可观。现代做法是用 HTTP/2 多路复用减少往返开销,同时做合理的资源合并,但要注意 HTTP/2 下过度合并反而可能不利于缓存(一个大文件变了会导致整个文件失效),所以更推荐保持适度拆分 + 文件名版本化。
先看一组量级参考(经验值,实际因站点差异很大):经过 Gzip 压缩的文本资源体积通常能降到原来的两成到四成;图片用 WebP 格式通常比同等质量的 JPEG 小两三成;开启长期缓存后,重复访问的静态资源请求基本不再产生流量。这三招叠加,一个没优化过的站点减少一半以上的传输量很常见。
二、Nginx 的过期头与缓存头
静态资源加速的第一站是源站 Nginx。哪怕你用了 CDN,源站的缓存头也会决定 CDN 的行为(除非在 CDN 侧覆盖规则)。
Nginx 用 expires 指令和 add_header 来设置缓存头。核心原则是:内容越稳定,缓存时间越长;文件名带版本号的资源,可以设到一年。
# 放在 http 或 server 块内
# 图片、字体、媒体文件:缓存一年,且禁止转换(immutable)
location ~* \.(png|jpg|jpeg|gif|webp|avif|ico|svg|woff2?|ttf|eot|mp4)$ {
root /var/www/site/static;
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
# CSS/JS:如果文件名带哈希指纹,同样可以设一年
location ~* \.(css|js)$ {
root /var/www/site/static;
expires 365d;
add_header Cache-Control "public, immutable";
}
# HTML:不缓存或者很短缓存,交给浏览器协商
location ~* \.html$ {
root /var/www/site;
expires -1;
add_header Cache-Control "no-cache";
}
几个要点要说明:
expires 365d 会同时设置 Expires 和 Cache-Control: max-age;immutable 告诉浏览器在有效期内不要发协商请求,连 304 都省了,适合带指纹的资源;expires -1 表示立即过期,即不使用强缓存,每次都要协商。
但要注意一个坑:add_header 在 location 里配置会覆盖外层的 add_header,如果只想要某个头而外层还有别的头,可能会丢掉。用 always 参数可以保证在某些状态码下也发送(比如 404)。
改完配置务必先测再 reload,留好回滚:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%F)
nginx -t && systemctl reload nginx
# 回滚:cp /etc/nginx/nginx.conf.bak.$(date +%F) /etc/nginx/nginx.conf && nginx -t && systemctl reload nginx
验证响应头是否正确:
curl -I https://www.example.com/static/app.abc123.css
# 期望看到 Cache-Control: public, max-age=31536000, immutable
# 以及 expires 头(缓存一年)
三、Nginx 反向代理缓存 proxy_cache
对于放在 Nginx 后面的应用(比如后端是 PHP、Java、Node),还可以在 Nginx 这一层做反向代理缓存,把后端生成的结果缓存起来,减少后端压力。注意:反向代理缓存主要针对可缓存的动态结果或中间层,纯静态文件本身已经由文件系统直接提供,不需要再走 proxy_cache。
# 放在 http 块内:定义缓存区
# levels=1:2 是目录层级,keys_zone 是共享内存名和大小,max_size 是磁盘缓存上限
proxy_cache_path /var/cache/nginx/proxy_cache
levels=1:2
keys_zone=my_cache:100m
max_size=10g
inactive=7d
use_temp_path=off;
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 启用上面定义的缓存区
proxy_cache my_cache;
# 只缓存 200 和 302,且缓存 10 分钟
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
# 允许在源站不可用时使用过期缓存,提升可用性
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
# 缓存 key,默认是 scheme+host+uri
proxy_cache_key "$scheme$host$request_uri";
# 在响应头里加缓存状态,方便调试
add_header X-Cache-Status $upstream_cache_status;
}
}
$upstream_cache_status 是调试神器,它会在响应头里显示 HIT、MISS、EXPIRED、STALE 等状态,一眼就能看出缓存有没有生效。
# 创建缓存目录(先确认路径存在)
mkdir -p /var/cache/nginx/proxy_cache
chown -R nginx:nginx /var/cache/nginx # Debian/Ubuntu 上用 www-data
nginx -t && systemctl reload nginx
# 验证缓存状态
curl -I https://www.example.com/ | grep -i x-cache-status
# 第一次 MISS,第二次应该是 HIT
其他几个常用指令:
| 指令 | 作用 |
|---|---|
proxy_cache_valid |
按状态码设置缓存时间 |
proxy_cache_min_uses |
被请求多少次后才缓存,避免缓存只访问一次的冷内容 |
proxy_cache_bypass |
满足条件时跳过缓存直接回源 |
proxy_no_cache |
满足条件时不把响应写入缓存 |
proxy_cache_use_stale |
后端故障时用过期缓存兜底 |
proxy_cache_bypass 和 proxy_no_cache 最典型的用法是配合 Cookie:登录用户(带特定 Cookie)的请求跳过缓存,避免把某个人的私人页面缓存下来给别人看。
四、开启压缩:Gzip 与 Brotli
压缩是"让文件变小"的主要手段,对 HTML、CSS、JS、JSON、SVG 这类文本资源效果最好,通常能压到原来的两三成。
# 放在 http 块内
gzip on;
gzip_vary on;
gzip_min_length 1024; # 小于 1KB 不压缩,压了反而可能变大
gzip_comp_level 5; # 压缩级别 1-9,5 是速度和压缩比的平衡点
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/json
application/xml
application/rss+xml
image/svg+xml;
# 注意:图片(jpg/png)、视频、字体已经是压缩格式,再 gzip 没有意义,不要加进去
Brotli 的压缩率通常比 Gzip 更好,尤其对文本资源。Nginx 默认不带 Brotli 模块,需要额外安装(很多发行版有现成包,或者编译动态模块)。
# Debian/Ubuntu:安装 brotli 模块
apt update
apt install libnginx-mod-brotli
# 安装后重载
nginx -t && systemctl reload nginx
# RHEL/CentOS/Rocky:通常需要 EPEL 或自行编译
yum install epel-release
yum install nginx-mod-brotli # 具体包名视仓库而定
# Brotli 配置(模块加载后)
brotli on;
brotli_comp_level 5;
brotli_static on; # 如果有预压缩的 .br 文件,直接使用
brotli_types
text/plain
text/css
application/javascript
application/json
image/svg+xml;
使用前要确认浏览器和 CDN 都支持 Brotli,并注意 brotli 与 gzip 同时开启时的协商顺序。多数 CDN 服务商也支持在选择源站响应时自动压缩,可以根据实际情况决定压缩放在源站还是 CDN 侧。
五、动静分离与资源指纹
动静分离是把静态资源和动态请求分开处理:静态资源用独立域名(或独立目录)承载,可以放心设长缓存;动态请求留在主域名,不受静态缓存规则影响。这是让缓存策略互不干扰的关键布局。
主站: www.example.com → 动态请求,不缓存或短缓存
静态: static.example.com → 静态资源,接入 CDN,长缓存
独立静态域名的好处:缓存策略可以独立配置;静态域名可以走无 Cookie 的请求,避免 Cookie 拖累缓存命中;将来换 CDN 或换存储只动一个域名。
资源指纹(版本化)是让长缓存能安全使用的前提。做法是给静态资源文件名加上内容哈希,比如 app.9f8e7d6c.js。内容一变,哈希就变,文件名就变,浏览器和 CDN 自然拿到新文件;内容不变,文件名不变,缓存永远命中。这样你就敢放心地把缓存设成一年。
# 用 md5 或 sha 给文件生成内容哈希,重命名(构建流程里通常由打包工具自动完成)
f="app.js"
hash=$(md5sum "$f" | cut -c1-8)
cp "$f" "app.$hash.js"
echo "生成:app.$hash.js"
# 主流前端构建工具(webpack/vite)都支持 filename 带 [contenthash],配置后自动生成
六、CDN 侧的缓存规则配置思路
静态资源上了 CDN 之后,CDN 侧的缓存规则决定最终效果。配置思路和 Nginx 一致:按文件类型分层设缓存时间,并处理好参数和 Cookie。
| 资源类型 | CDN 缓存时间建议 | 备注 |
|---|---|---|
| 带哈希的 CSS/JS | 一年 | 最理想,靠文件名版本化更新 |
| 图片、字体、媒体 | 三十天到一年 | 更新少,适合长缓存 |
| HTML 页面 | 几分钟到几小时 | 视更新频率,不要过长 |
| 带参数的静态资源 | 一年(并开启忽略参数) | 忽略埋点等无意义参数 |
| 动态接口 | 不缓存 | 走动态加速优化链路即可 |
在 CDN 控制台里通常可以按"文件扩展名""目录""URL 路径"来配规则。推荐的做法是:给静态域名 static.example.com 整站配长缓存,再对少数特殊路径单独覆盖。
同时要检查几个 CDN 侧的开关:是否忽略 URL 参数、是否对带 Cookie 的请求不缓存、是否按状态码缓存、是否强制 HTTPS。这些开关配错,Nginx 那边设的缓存头可能被 CDN 覆盖掉。
# 验证 CDN 返回的缓存头是否符合预期
curl -I https://static.example.com/app.9f8e7d6c.js
# 关注 Cache-Control、Expires、Age、以及 CDN 的缓存状态字段
curl -I "https://static.example.com/logo.png?v=1"
curl -I "https://static.example.com/logo.png?ts=123456"
# 如果两个都 HIT 且 Age 一致,说明参数被忽略了,命中率会很高
七、图片与视频的额外优化
图片通常占网页流量的大头,值得单列。几项措施:按实际显示尺寸生成图片(不要用 2000 像素的大图显示成 200 像素的缩略图);用 WebP 或 AVIF 等现代格式;开启懒加载,让首屏外的图片等滚动到时再加载;给图片设好宽高避免布局抖动。
视频则要考虑分片、按需加载、以及是否用专门的点播/直播 CDN 方案。视频文件本身是压缩格式,不要对它做 Gzip,但可以配置较长的缓存时间和支持 Range 请求(断点续传、拖动播放)。
# Nginx 支持 Range 请求(默认开启),确保大文件能断点续传、视频能拖动
location ~* \.(mp4|webm|flv)$ {
root /var/www/site/media;
expires 30d;
add_header Cache-Control "public";
# mp4 通常不需要压缩,避免 CPU 浪费
}
常见问题
Q:静态资源设一年缓存,更新后用户看不到新版本怎么办?
用文件名版本化(加内容哈希),更新时文件名变化,用户自然拿到新文件。如果没做版本化又设了长缓存,只能手动刷新 CDN 缓存并让用户强刷浏览器。
Q:expires 和 Cache-Control 谁优先?
浏览器优先看 Cache-Control,Expires 是兼容老客户端的补充。用 expires 指令时 Nginx 会同时设置两者,一般不会冲突。要精确控制就用 add_header Cache-Control。
Q:Gzip 和 Brotli 要同时开吗?
可以同时开,Nginx 会按客户端 Accept-Encoding 协商。如果客户端两种都支持,通常会优先给 Brotli。要注意压缩级别不要设太高(级别 9 CPU 消耗大,收益有限)。
Q:图片要不要做 Gzip?
不要。JPEG、PNG、WebP 本身就是压缩格式,再 Gzip 几乎没有收益,还可能因为 CPU 消耗而拖慢响应。只对文本类资源开压缩。
Q:proxy_cache 和 CDN 缓存会不会冲突?
不会,它们是两级缓存。用户请求先到 CDN,未命中时回源到 Nginx,Nginx 再决定命中自己的 proxy_cache 还是打到后端。两级缓存配合得好可以大幅降低后端压力。
总结
静态资源加速的两条主线是"让文件变小"和"让文件不用重复下"。压缩解决前者,缓存解决后者,两者都要做。
Nginx 侧用 expires 和 Cache-Control 设置缓存头,内容是稳定型就设长缓存,带哈希的文件名可以设一年并加 immutable。反向代理缓存用 proxy_cache 系列指令,配 $upstream_cache_status 方便调试。
压缩优先对文本资源开 Gzip,条件允许再上 Brotli;图片、视频、字体不要做文本压缩。压缩级别选中间档,兼顾速度和效果。
动静分离是布局基础:静态资源用独立域名承载,可以设长缓存又不影响动态请求,还避免 Cookie 污染缓存。资源文件名加内容哈希是安全使用长缓存的前提。
CDN 侧按文件类型分层配缓存时间,处理好忽略参数、Cookie、状态码缓存这几个开关,并确保不覆盖源站想表达的缓存意图。图片按尺寸生成、用现代格式、开懒加载,视频支持 Range 请求。
把这几步做完,静态资源这一块的加载速度和带宽占用通常会有肉眼可见的改善,而且这套配置是"一次配好、长期受益"的。
企业QQ咨询




