静态博客的传输层优化:HTTP/2、gzip 和缓存头
先量化,再优化
重装之后顺手给站点做了一次体检,四个数字最扎眼:
| 指标 | 数值 |
|---|---|
| HTTPS | 只有 HTTP/1.1 |
| gzip | 只压 HTML,CSS/JS 全裸奔 |
| 图片缓存头 | 没有 |
| 图床里没人引用的死图 | 40 个,占 6.5MB / 8.9MB |
前三个都是 nginx 一行配置的事,最后一个是长期积累的垃圾。
HTTP/2:一个连接跑完所有资源
HTTP/1.1 的问题是「一个连接同时只能处理一个请求」,浏览器只能靠开 6 个并发连接硬堆。页面有几十个资源的时候,排队是实打实的延迟。
开启只需要在 listen 后面加一个词:
listen 443 ssl http2; |
验证别只看配置,要实际协商:
echo | openssl s_client -connect 127.0.0.1:443 -servername 你的域名 -alpn h2 2>/dev/null \ |
看到 ALPN protocol: h2 才算真的开了。
gzip:默认只压 HTML
这是最容易忽略的一项。nginx 的 gzip on; 打开之后,gzip_types 的**默认值只有 text/html**——CSS、JS、JSON、XML 一个都不压。
Ubuntu 的默认配置里这一行是注释掉的,很多人以为开了 gzip 就万事大吉:
gzip on; |
验证:
curl -s -o /dev/null -H "Accept-Encoding: gzip" \ |
我这个站上的 CSS 压完大概剩三成。
缓存头:让重复访问变成零请求
图片走 nginx 反代到对象存储,响应里只有 ETag 和 Last-Modified,没有 Cache-Control——意味着浏览器每次都要回源确认一遍。图多的页面,读者翻两页就能感觉到。
location ^~ /cykblog/ { |
静态资源同理:
location ~* \.(?:css|js|jpg|jpeg|png|gif|webp|svg|ico|woff2?|ttf|eot)$ { |
注意一个 add_header 的坑:location 里一旦写了 add_header,server 层的 add_header 就不再继承。所以安全响应头要在每个自己写了 header 的 location 里重复一遍,否则 /cykblog/ 会漏掉。
server_tokens 和安全头
server_tokens off; |
关掉版本号不是为了「安全多少」,是为了别主动告诉扫描器「我是 nginx 1.24.0,你可以去查这个版本的 CVE」。
HSTS 我一开始只给了 30 天,没设 preload。这个头一旦下发,浏览器在有效期内会强制走 HTTPS——证书出问题时网站会直接打不开,所以先短后长比较稳。
第三方 CDN 的两个教训
第一个:图标字体加载失败。 页面里引用了一个国内 CDN 的 iconfont,报 net::ERR_CONNECTION_CLOSED。在服务器上直接抓那个文件是 200,说明 CDN 本身没问题,是我本地到它的链路被掐了。
解决办法不是换 CDN,是把字体搬到自己的域名下:
curl -o iconfont.css "https://原CDN/iconfont.css" |
CSS 里用的是相对路径(url('iconfont.woff2')),放同一个目录就能自动找到,不用改。
第二个:统计脚本报 ERR_BLOCKED_BY_ORB。 一个免费访问量统计服务,请求失败后还会挂两分钟才超时,直接把首屏拖慢。关掉,换成 Cloudflare 的 Web Analytics(无 cookie、脚本小、国内可达)。
图床清理:40 个死图占 73% 空间
把桶里所有对象和「页面里实际引用到的」做了一次差集,结果很难看:
对象总数 80,被引用 51,无人引用 40,占用 6.5MB / 8.9MB |
最大的一个是一张 2560×1440 的 PNG 壁纸,4MB——而且同一张图我早就转过 webp(104KB),只是忘了删 PNG,也没在用。
清理时踩了一个坑:用文件名直接 grep HTML 会漏。中文名和带反引号的文件名在 HTML 里是 URL 编码的:
磁盘:`scroll-view`滑动布局.webp |
第一次按文件名匹配,把它们当成「无人引用」删了,结果 12 张正在用的图直接 404。还好删之前打了包,全部恢复。
正确的做法是先解码再比对,而且要同时扫 html / css / js 三类文件:
for m in re.findall(r'cykblog/([^"\'\s)<>?#]+)', txt): |
改完之后桶从 8.9MB 降到 3.0MB,引用完整性校验为 0 缺失。
上传链路:把规范写进工具
以前传图是「拖进去就完事」,于是文件名长这样:
image-20240729225711008.png |
现在用 PicGo 传,把规则配死:
- 路径:
posts/{年}/{月}/(对象存储里的目录) - 文件名:
{y}{m}{d}-{rand:6},例如20261027-a3f9c2.webp
有个细节值得记一下:PicGo 的 MinIO 插件里,pathFormat 只支持 {bucket} 一个占位符,**不支持 {年}/{月}**。如果照着别的插件文档填,最后会生成一个字面量路径 posts/{年}/{月}/,对象存的路径和返回的 URL 对不上,图片必然 404。
效果
改动都不大,但叠加起来:
- 资源请求从「串行排队」变成「一个连接并发」
- CSS/JS 传输量降到三成左右
- 重复访问的图片基本不再回源
- 图床体积减少 66%
- 首屏少了一个会挂两分钟的第三方请求
都是教科书里的东西,只是以前觉得「博客而已,无所谓」。
