图片优化:免费额度内能做的四件事
读完这篇你能做到:在不购买 Cloudflare 图片服务的前提下,把站点的图片体积压到合理范围,让首屏不因图片拖慢,并且知道哪些优化是构建期一次做完、哪些需要运行时处理而免费档做不到。
先说清边界。Cloudflare 的图片产品(Images、Image Resizing、Polish)属于付费套餐或附加服务,免费档不开。所以免费路径只有一条:在构建阶段把图处理好,上传已经优化过的文件。这条路径的效果不比运行时差,代价是原图改动后要重新构建。
1. 先量化,再动手
不看数据就压图,容易压错地方。三个数值得先拿到:
单张图片的字节数。打开构建产物目录,按大小排序,前 10 个文件通常占了整站体积的八成。
首屏图片的数量。首屏指不滚动就能看到的区域,这部分图片影响 LCP,其余的影响很小。
图片请求占页面总请求的比例。图片多、单张小的站点,收益在减少请求数;图片少、单张大的,收益在压体积。
用浏览器开发者工具的网络面板按 Size 排序就能看到这些,不需要额外工具。
2. 尺寸分级
一张 2400 像素宽的图放进 360 像素宽的卡片里,多出来的像素全是浪费。做法是构建期生成几个宽度,让浏览器按视口挑:
<picture>
<source
type="image/avif"
srcset="/img/cover-480.avif 480w, /img/cover-960.avif 960w, /img/cover-1440.avif 1440w"
sizes="(max-width: 600px) 100vw, 720px"
/>
<img
src="/img/cover-960.webp"
width="1440"
height="810"
alt="站点封面"
loading="lazy"
decoding="async"
/>
</picture>
三个要点:srcset 给候选宽度,sizes 告诉浏览器实际显示宽度,width 与 height 保住宽高比。少了 sizes,浏览器会按最大那张下载。
宽度档位不必多,480 / 960 / 1440 三档覆盖绝大多数场景。档位越多构建越慢,收益递减。
3. 现代格式
WebP 相比 JPEG 通常小 25% 到 35%,AVIF 再小一些但编码慢。构建期转换一次,之后不再付出成本。
Astro 用内置的图片组件,导入即处理:
---
import { Image } from 'astro:assets';
import cover from '../assets/cover.jpg';
---
<Image src={cover} alt="站点封面" widths={[480, 960, 1440]} formats={['avif', 'webp']} />
Hugo 用资源管道,按宽度循环生成:
{{ $img := resources.Get "images/cover.jpg" }}
{{ $small := $img.Resize "480x webp" }}
{{ $large := $img.Resize "1440x webp" }}
<img src="{{ $large.RelPermalink }}" srcset="{{ $small.RelPermalink }} 480w, {{ $large.RelPermalink }} 1440w"
sizes="(max-width: 600px) 100vw, 720px" width="{{ $img.Width }}" height="{{ $img.Height }}" alt="站点封面">
纯手写站用 sharp 写个脚本,在构建命令前跑一遍,逻辑一样。框架的具体构建参数在 71-static-site-astro-hugo.md。
4. 加载优先级
浏览器默认按文档顺序发现图片。首屏大图要显式提权,屏幕外的图要显式降权。
| 属性 | 用在哪 |
|---|---|
fetchpriority="high" | 首屏唯一的主图 |
loading="lazy" | 首屏之外的所有图 |
decoding="async" | 不阻塞渲染的图 |
loading="eager" | 首屏第一张,其余不用 |
最容易犯的错是给所有图都加 lazy。首屏图懒加载会让 LCP 变差,因为浏览器要等布局完成才开始下载。反过来,给几十张图都设 high 等于没设。
5. 大图与缓存
单文件超过 25 MiB 时 Pages 传不上去,这是硬边界。视频、原始摄影图、设计稿这类文件放 R2,用子域指过去,产物里只留 URL 和尺寸。文件数与体积的完整额度见 56-free-tier-limits-cheatsheet.md。
命名的稳定性影响缓存。带内容哈希的文件名(cover.a1b2c3.webp)可以设长缓存,每次内容变化自然换名;固定文件名(cover.webp)只能靠较短的 TTL,否则用户看到旧图。缓存头写在 _headers 里:
/img/*
Cache-Control: public, max-age=31536000, immutable
immutable 只对带哈希的文件名使用,用在固定名上会导致改图不生效。
6. 效果对照
| 手段 | 典型收益 | 一次性还是持续 |
|---|---|---|
| 尺寸分级 | 体积降 40% 到 70% | 构建期一次 |
| 转 WebP / AVIF | 体积再降 25% 到 35% | 构建期一次 |
| 宽高占位 | 消除布局偏移 | 一次性 |
| 加载优先级 | LCP 改善几百毫秒 | 一次性 |
| 长缓存加哈希名 | 回访零请求 | 一次性 |
前两项是体积,后三项是体验。体积决定首次加载,体验决定用户感知到的速度。
常见问题
免费档能不能用 /cdn-cgi/image/ 做实时缩放。
不能。这个端点属于 Image Resizing,需要付费套餐或附加服务;免费档请求它会失败。想在运行时按需缩放,替代方案是用 Worker 加 R2 自己做,成本是每次请求都要跑代码。
WebP 和 AVIF 都生成,体积会不会翻倍。
会,磁盘上多一份文件,但每个用户只下载其中一种。浏览器按 <source> 顺序选,支持 AVIF 的走 AVIF,不支持的回落到 WebP。存储成本换传输成本,静态托管下这个交换很划算。
图片压缩后变糊了怎么办。
不要一次压到底。quality 从 80 起步,肉眼看不出差别就停;照片类内容 75 到 82 通常够,带文字的截图要更高或者干脆不压。压完拿原图并排对比,别只看体积数字。
小结
免费档的图片优化没有魔法,全部发生在构建期:生成几个宽度、转成现代格式、写上宽高、标好加载优先级。四件事做完,多数站点的图片体积能降一半以上。
25 MiB 是上传边界,超过就外置到 R2。缓存策略和文件命名要一起设计,哈希名配长缓存,固定名配短 TTL。这两条在 74-cache-rules.md 里有更完整的规则设计。
数据来源与检索时间
检索时间 2026-11-10。
| 来源 | URL | 对应数据 |
|---|---|---|
| Pages Limits | https://developers.cloudflare.com/pages/platform/limits/ | 单文件 25 MiB 上限,超大文件建议放 R2 公共桶并用子域提供 |
| Workers 静态资源 | https://developers.cloudflare.com/workers/static-assets/ | 静态资源随 Worker 一起部署、命中文件不进代码,用于判断图片存放位置 |
| Astro 图片文档 | https://docs.astro.build/en/guides/images/ | astro:assets 的 Image、widths、formats 用法 |
| Hugo 图片处理 | https://gohugo.io/content-management/image-processing/ | resources.Get 与 .Resize 生成多尺寸 |
核验说明
- 文档部分:25 MiB 上限来自 2026-11-10 抓取的 Pages Limits 页面。Astro 与 Hugo 的图片 API 依据各自官方文档,示例代码按文档签名写。
- 未执行:本机无 Zone 权限,无法开通 Polish 或 Image Resizing,正文中"免费档不能用
/cdn-cgi/image/"这一判断依据套餐划分,未实测端点返回。 - 未执行:示例代码没有在 Pages 项目里跑过,
sharp脚本未编写也未运行。 - 未验证:体积下降的百分比是行业常见区间,不是本站实测数据,正文用"通常""典型"标注。
- 本地构建验证:无。