图片与字体自托管:少一次外部请求,少一个故障点
引一个 CDN 链接只要一行,但代价是:多一次 DNS 查询、多一次 TLS 握手、多一个可能挂掉或被墙的第三方。自己的东西放在自己手里最稳。
一、第三方 CDN 的隐性成本
每次引用外部资源,浏览器都要走一遍完整流程:
DNS 查询 → TCP 握手 → TLS 握手 → 下载资源对 fonts.googleapis.com 这种跨域资源,光握手就是 2~3 个 RTT。在国内网络环境下,这一步经常要几百毫秒甚至超时。
更麻烦的是故障面变大:CDN 挂了、被墙了、改版了,你的站点就跟着出问题,而你什么都做不了。
二、字体自托管
步骤
- 下载字体文件(优先 woff2,压缩率最高);
- 放到自己的目录;
- 用
@font-face声明; - 加
preload提前加载。
@font-face {
font-family: "MyFont";
src: url("/fonts/myfont.woff2") format("woff2");
font-weight: 400;
font-display: swap; /* 关键:字体没加载完先用系统字体显示 */
}
body {
font-family: "MyFont", -apple-system, "PingFang SC", sans-serif;
}font-display: swap 很重要:不加的话字体加载期间文字是空白的(FOIT),用户会以为页面坏了;加了会先用后备字体显示,字体到位后再切换(FOUT)。
<!-- 关键字体预加载 -->
<link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>
crossorigin属性哪怕是同域也必须加,否则字体会被下载两次。
中文站点的现实选择
中文字体动辄 5~10MB,自托管不现实。最实用的方案是不用自定义字体:
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;直接用系统内置的苹方、微软雅黑,零请求、零加载、每个平台都好看。本站就是这么做的——最有效的优化是不加载。
如果确实需要中文特殊字体,考虑子集化:只保留站点实际用到的字。
# 用 pyftsubset 按实际用到的字符裁剪(几个 MB → 几十 KB)
pip install fonttools brotli
pyftsubset source.ttf --text-file=used-chars.txt --flavor=woff2 --output-file=subset.woff2三、图标:用 SVG 或图标字体
别引 Font Awesome 全套(几百 KB),只要几个图标的话:
- 内联 SVG(最推荐):零请求,还能用 CSS 改颜色;
- SVG sprite:多个图标合成一个文件;
- 单个 SVG 文件:
<img src="icon.svg">。
本站头部的搜索、主题切换图标就是内联 SVG,直接写在 HTML 里,随页面一起到达。
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="7"></circle>
<path d="M20 20l-3.5-3.5"></path>
</svg>用 currentColor 的话,图标颜色会自动跟随父元素的 color,主题切换时不用改两处。
四、图片:格式、尺寸、压缩
格式怎么选
| 格式 | 适用 | 特点 |
|---|---|---|
| WebP | 通用首选 | 比 JPEG 小 30%,现代浏览器全支持 |
| AVIF | 极致压缩 | 比 WebP 再小 20%,编码慢 |
| JPEG | 照片 | 兼容性最好 |
| PNG | 需要透明 / 截图 | 无损,体积大 |
| SVG | 图标、示意图 | 矢量,任意缩放 |
<!-- 用 picture 做优雅降级 -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="描述" width="800" height="450" loading="lazy">
</picture>转换与压缩
sudo apt install -y webp imagemagick
# 转 WebP
cwebp -q 80 input.jpg -o output.webp
# 批量转
for f in *.jpg; do cwebp -q 80 "$f" -o "${f%.jpg}.webp"; done
# 压缩 JPEG(原地)
jpegoptim --max=85 *.jpg
# 压缩 PNG
optipng -o2 *.png尺寸要匹配用途
别用 2000px 的原图当 300px 的缩略图。给出不同尺寸让浏览器挑:
<img srcset="img-480.webp 480w, img-800.webp 800w, img-1200.webp 1200w"
sizes="(max-width: 600px) 480px, 800px"
src="img-800.webp" alt="" width="800" height="450" loading="lazy">五、必须写 width / height
图片没有尺寸声明时,加载完成那一刻页面会突然往下顶,用户正要点的按钮被顶走了。这个指标叫 CLS(累积布局偏移),是 Lighthouse 的核心评分项之一。
<!-- 好:预留空间,加载时不跳动 -->
<img src="a.webp" width="800" height="450" loading="lazy" alt="">
<!-- 也可以只给比例,让 CSS 决定实际尺寸 -->
img { aspect-ratio: 16 / 9; width: 100%; height: auto; }六、preload 要克制
preload 是「提前下载」,但不是加得越多越好——它会跟真正重要的资源抢带宽。
只 preload 三类东西:
- 首屏关键字体;
- 首屏最大的那张图(LCP 元素);
- 阻塞渲染的关键 CSS。
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero.webp" as="image">其他资源用 prefetch(空闲时下载下一页可能用到的)或干脆不预加载。
七、检查清单
# 找出页面里所有外部域名
curl -s https://www.bzii.cn | grep -oE "https?://[a-z0-9.-]+" | sort -u
# 理想的静态站:外部域名数量 = 0- 无第三方 CDN 依赖(JS/CSS/字体/图标)
- 图片为 WebP/AVIF,尺寸与用途匹配
- 图片写了 width/height 或 aspect-ratio
- 非首屏图片有
loading="lazy" - 字体用
font-display: swap,或干脆用系统字体 - preload 只用在真正关键的资源上
一句话原则:能不加载的就不加载,能自己托管的就自己托管。
