本文约 4,038 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:换了 CDN,图片还是慢在「尺寸」上
把图片搬到 CDN 是提速的第一步,但很多站点做完之后,PageSpeed Insights 依旧提示「图片尺寸不符合展示大小」。原因通常是:CDN 只是把同一个文件搬到了更近的节点,而浏览器拿到的仍然是那张 1600px 的原图,展示区域却只有 360px 宽。
要让图片真正「自适应」,靠的是 WordPress 的响应式图片机制:服务端在 img 标签上输出一组候选地址(srcset)和一组尺寸提示(sizes),浏览器根据自己的视口宽度和屏幕像素密度挑选最合适的一个。CDN 的职责则是把这些候选地址都加速、并且按需裁剪。两者配合,才算完整的图片加速方案。只做一半,效果就只剩一半。
原理分析:srcset 是怎么算出来的
WordPress 在输出图片时,会把 _wp_attachment_metadata 里记录的所有尺寸按宽度排序,生成 srcset。这个过程由 wp_calculate_image_srcset() 完成,核心规则有三条:
- 只收录宽度不超过
max_srcset_image_width的候选,默认值 1600px,可通过同名过滤器调整; - 不会收录比原图还宽的尺寸,避免浏览器拿到被放大的图;
- 宽高比必须与原图一致,否则会被剔除。
与此同时,sizes 属性由 wp_calculate_image_sizes() 生成,默认大概是「在最大宽度内占满 100% 视口」这样的描述,主题可以通过 wp_calculate_image_sizes 过滤器改写。如果 sizes 写得不准,浏览器选图的依据就错了——这是很多人忽略的一环。
| 环节 | 负责的钩子 / 函数 | 常见调整动作 |
|---|---|---|
| 生成候选尺寸 | add_image_size()、big_image_size_threshold |
增加断点尺寸、限制超大图 |
| 输出 srcset | wp_calculate_image_srcset |
替换为 CDN 域名、扩展候选 |
| 输出 sizes | wp_calculate_image_sizes |
按主题布局声明真实展示宽度 |
| 图片地址 | wp_get_attachment_url、wp_get_attachment_image_src |
整体接管为 CDN 地址 |
另外,big_image_size_threshold 默认 2560px,超过这个宽度的上传图会被自动缩放成 -scaled 版本。如果你的 CDN 具备裁剪能力,可以适当调低这个值,减少源站存储压力。
保姆级实操:让 CDN 真正接管图片
步骤1:规划断点尺寸。 先确认主题实际用到的尺寸,避免生成一堆没人用的缩略图。移动端为主的外贸站,三到四个断点通常就够。
<?php
// 注册响应式断点尺寸
add_action( 'after_setup_theme', function () {
add_image_size( 'card-480', 480, 0, false );
add_image_size( 'card-768', 768, 0, false );
add_image_size( 'card-1200', 1200, 0, false );
} );
// 提高 srcset 的宽度上限,适配 2x 屏
add_filter( 'max_srcset_image_width', function () {
return 2560;
} );
步骤2:把图片地址整体换成 CDN 域名。 用 wp_get_attachment_url 过滤所有附件地址,同时把 srcset 里的每一个候选也换掉。注意只替换 uploads 路径,避免把后台链接也改坏。
<?php
// 将 uploads 地址接管到 CDN 域名
add_filter( 'wp_get_attachment_url', function ( $url ) {
$uploads = wp_get_upload_dir();
if ( 0 === strpos( $url, $uploads['baseurl'] ) ) {
return str_replace( $uploads['baseurl'], 'https://img.example-cdn.com', $url );
}
return $url;
} );
add_filter( 'wp_calculate_image_srcset', function ( $sources ) {
foreach ( $sources as $w => &$src ) {
$src['url'] = str_replace(
wp_get_upload_dir()['baseurl'], 'https://img.example-cdn.com', $src['url']
);
}
return $sources;
} );
步骤3:配置 CDN 回源。 在 CDN 控制台把源站设为站点域名,缓存规则针对 /wp-content/uploads/ 设置较长 TTL(如 30 天),并开启忽略 query string(WordPress 常带 ?ver= 参数)。如果站点同时输出 WebP,务必确认 CDN 会按 Accept 头分别缓存,这一点在用插件自动转 WebP 并兼容旧浏览器中反复强调过。
步骤4:校对 sizes 属性。 默认的 sizes 往往偏大,会让浏览器挑到过大的图。按主题实际布局声明,例如两栏文章页里正文图片最大 720px:
<?php
add_filter( 'wp_calculate_image_sizes', function ( $sizes, $size, $image_src, $image_meta, $attachment_id ) {
return '(max-width: 720px) 100vw, 720px';
}, 10, 5 );
步骤5:用插件方案时的取舍。 不想写代码的话,可以选成熟插件:WP Offload Media 负责把媒体库搬到 S3/对象存储并重写 URL,CDN Enabler 负责把静态资源前缀替换成 CDN 域名,Cloudflare 官方插件配合 APO 可以整页加速。它们的共同点都是「重写 URL + 回源」,区别只在于存储位置是否迁移。
步骤6:验证命中情况。 用 curl 看响应头里的缓存状态字段,连续请求两次,第二次应当命中缓存。
for i in 1 2; do
curl -sI https://img.example-cdn.com/wp-content/uploads/2026/01/demo-768x512.jpg \
| grep -iE 'HTTP/|x-cache|cf-cache-status|age|content-length'
echo '---'
done
步骤7:观察实际传输体积。 在 DevTools 里切换到不同视口宽度,确认 srcset 选中的是不同尺寸而不是同一张大图。
常见踩坑
- 混合内容警告。站点是 HTTPS 而 CDN 域名只配了 HTTP,浏览器直接拦截图片。CDN 必须配置有效证书。
- 后台编辑器图片 404。过滤
wp_get_attachment_url时没判断条件,把后台、REST API 里的地址也换成了 CDN,而 CDN 尚未回源成功。建议只对前台生效(可用is_admin()判断)。 - 改了图片但前台不更新。CDN 与浏览器双重缓存,需要刷新 CDN 缓存并等待 TTL。WordPress 会带
?ver=,也可通过改文件名规避。 - 防盗链 Referer 把自家后台挡在门外。CDN 开启了 Referer 白名单,却没放行站点后台域名,导致编辑器里图片打不开。
- srcset 候选里出现超大图。忘记限制
max_srcset_image_width,把 2560px 的图塞进了候选,移动端也可能被挑中。 - 案例:某站点接入 CDN 后,图片请求全部 403。排查发现 CDN 侧开启了「回源鉴权」,而源站 Nginx 又有 Referer 防盗链规则,两者叠加导致回源请求被源站拒绝。最终在 Nginx 里对 CDN 回源 IP 段放行,并在 CDN 控制台配置正确的回源 Host,问题才消除。这类问题的通用排查顺序是:直接访问源站 → 直接访问 CDN → 对比两者响应头。
- 跨域字体与 canvas 污染。图片 CDN 未返回
Access-Control-Allow-Origin,导致 canvas 导出图片或某些 JS 库报错。 - 对象存储桶被公开读写。用插件迁移到 S3/COS 时,务必确认桶策略是「公共读、私有写」,不要把密钥写进主题代码。
总结
图片 CDN 的收益来自两件事:距离更近和尺寸更准。前者靠 CDN 节点,后者靠 srcset + sizes 的正确输出,缺一不可。落地时按这个顺序做:规划断点尺寸 → 用过滤器接管 URL 与 srcset → 配置回源与缓存策略 → 校对 sizes → curl 验证命中 → DevTools 复核选中尺寸。做完你会发现,真正让图片变快的往往不是换了个域名,而是浏览器终于只下载它需要的那一张。延伸阅读:图片懒加载插件的选型与原生 loading 属性。
