cloudflare-images by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-images状态 : 生产就绪 ✅ 最后更新 : 2026-01-21 依赖项 : 已启用 Images 的 Cloudflare 账户 最新版本 : Cloudflare Images API v2, @cloudflare/workers-types@4.20260108.0
近期更新 (2025) :
gravity=face 配合 zoom 控制,基于 GPU 的 RetinaFace,99.4% 精确度)弃用通知 : Mirage 已于 2025年9月15日弃用。请迁移至 Cloudflare Images 进行存储/转换,或使用原生的 <img loading="lazy"> 实现延迟加载。
两大功能:Images API (上传/存储及变体) 和 Image Transformations (通过 URL 或 Workers 调整任何图像大小)。
1. 启用 : 仪表板 → Images → 获取账户 ID + API 令牌 (Cloudflare Images: Edit 权限)
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
2. 上传 :
curl -X POST https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: multipart/form-data' \
-F 'file=@./image.jpg'
3. 提供 : https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/public
4. 转换 (可选): 仪表板 → Images → Transformations → 为区域启用
<img src="/cdn-cgi/image/width=800,quality=85/uploads/photo.jpg" />
1. 文件上传 : 使用 file (multipart/form-data) 向 /images/v1 发送 POST 请求,可选参数 id、requireSignedURLs、metadata
2. 通过 URL 上传 : 使用 url=https://example.com/image.jpg 发送 POST 请求 (支持 HTTP 基本认证)
3. 直接创作者上传 (一次性 URL,不暴露 API 密钥):
后端: 向 /images/v2/direct_upload 发送 POST 请求 → 返回 uploadURL
前端: 使用 FormData 将文件 POST 到 uploadURL
关键 CORS 修复 :
multipart/form-data (让浏览器设置请求头)file (而不是 image)/direct_uploadContent-Type: application/json/direct_uploadURL : /cdn-cgi/image/<OPTIONS>/<SOURCE>
width=800,height=600,fit=coverquality=85 (1-100)format=auto (自动检测 WebP/AVIF)gravity=auto (智能裁剪), gravity=face (AI 人脸检测,2025年8月正式发布), gravity=center, zoom=0.5 (范围 0-1,人脸裁剪紧密度)blur=10,sharpen=3,brightness=1.2scale-down, contain, cover, crop, padWorkers : 在 fetch 中使用 cf.image 对象
fetch(imageURL, {
cf: {
image: { width: 800, quality: 85, format: 'auto', gravity: 'face', zoom: 0.8 }
}
});
命名变体 (最多 100 个): 预定义的转换 (例如 avatar、thumbnail)
id、options 向 /images/v1/variants 发送 POST 请求imagedelivery.net/<HASH>/<ID>/avatar灵活变体 : URL 中的动态参数 (w=400,sharpen=3)
{"flexible_variants": true} 向 /images/v1/config 发送 PATCH 请求为私有图像生成 HMAC-SHA256 令牌 (URL 格式: ?exp=<TIMESTAMP>&sig=<HMAC>)。
算法 : HMAC-SHA256(signingKey, imageId + variant + expiry) → 十六进制签名
参见 : templates/signed-urls-generation.ts 获取 Workers 实现
✅ 为直接创作者上传使用 multipart/form-data
✅ 将文件字段命名为 file (而不是 image 或其他名称)
✅ 仅从后端调用 /direct_upload API (而不是浏览器)
✅ 为转换使用 HTTPS URL (不支持 HTTP)
✅ 对图像路径中的特殊字符进行 URL 编码
✅ 在使用 /cdn-cgi/image/ 之前,在区域上启用转换功能
✅ 为私有图像使用命名变体 (签名 URL)
✅ 检查 Cf-Resized 请求头以查找转换错误
✅ 设置 format=auto 以自动进行 WebP/AVIF 转换
✅ 使用 fit=scale-down 防止不必要的放大
❌ 为文件上传使用 application/json Content-Type
❌ 从浏览器调用 /direct_upload (CORS 将失败)
❌ 将灵活变体与 requireSignedURLs=true 一起使用
❌ 调整 SVG 文件大小 (它们本质上是可缩放的)
❌ 为转换使用 HTTP URL (仅限 HTTPS)
❌ 在 URL 中使用空格或未转义的 Unicode 字符
❌ 在 Workers 中多次转换同一图像 (导致 9403 循环错误)
❌ 超过 100 兆像素的图像尺寸
❌ 在 Workers 中使用 /cdn-cgi/image/ 端点 (请改用 cf.image)
❌ 在使用前忘记在区域上启用转换功能
此技能可预防 16 个已记录的问题。
错误 : Access to XMLHttpRequest blocked by CORS policy: Request header field content-type is not allowed
发生原因 : 服务器 CORS 设置仅允许 multipart/form-data 作为 Content-Type 请求头
预防措施 :
// ✅ 正确
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch(uploadURL, {
method: 'POST',
body: formData // 浏览器自动设置 multipart/form-data
});
// ❌ 错误
await fetch(uploadURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // CORS 错误
body: JSON.stringify({ file: base64Image })
});
错误 : 上传约 15 秒后出现 Error 5408
发生原因 : Cloudflare 有 30 秒的请求超时限制;上传速度慢或文件过大超过限制
预防措施 :
上传前压缩图像 (使用 Canvas API 在客户端进行)
使用合理的文件大小限制 (例如,最大 10MB)
向用户显示上传进度
优雅地处理超时错误
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
if (file.size > MAX_FILE_SIZE) { alert('文件过大。请选择小于 10MB 的图像。'); return; }
错误 : 400 Bad Request 并附带无用的错误信息
发生原因 : 文件字段必须命名为 file (而不是 image、photo 等)
预防措施 :
// ✅ 正确
formData.append('file', imageFile);
// ❌ 错误
formData.append('image', imageFile); // 400 错误
formData.append('photo', imageFile); // 400 错误
错误 : 预检 OPTIONS 请求被阻止
发生原因 : 直接从浏览器调用 /direct_upload API (应仅从后端调用)
预防措施 :
架构:
浏览器 → 后端 API → POST /direct_upload → 返回 uploadURL → 浏览器上传到 uploadURL
切勿将 API 令牌暴露给浏览器。在后端生成上传 URL,返回给前端。
错误 : Cf-Resized: err=9401 - 缺少必需的 cf.image 选项或选项无效
发生原因 : 缺少必需的转换参数或参数值无效
预防措施 :
// ✅ 正确
fetch(imageURL, {
cf: {
image: {
width: 800,
quality: 85,
format: 'auto'
}
}
});
// ❌ 错误
fetch(imageURL, {
cf: {
image: {
width: 'large', // 必须是数字
quality: 150 // 最大 100
}
}
});
错误 : Cf-Resized: err=9402 - 图像过大或连接中断
发生原因 : 图像超过最大面积 (100 兆像素) 或下载失败
预防措施 :
错误 : Cf-Resized: err=9403 - Worker 获取自己的 URL 或已调整大小的图像
发生原因 : 对已转换的图像应用转换,或 Worker 获取自身
预防措施 :
// ✅ 正确
if (url.pathname.startsWith('/images/')) {
const originalPath = url.pathname.replace('/images/', '');
const originURL = `https://storage.example.com/${originalPath}`;
return fetch(originURL, { cf: { image: { width: 800 } } });
}
// ❌ 错误
if (url.pathname.startsWith('/images/')) {
// 获取 Worker 自身的 URL,导致循环
return fetch(request, { cf: { image: { width: 800 } } });
}
错误 : Cf-Resized: err=9406 或 err=9419 - 非 HTTPS URL 或 URL 包含空格/未转义的 Unicode 字符
发生原因 : 图像 URL 使用 HTTP (而不是 HTTPS) 或包含无效字符
预防措施 :
// ✅ 正确
const imageURL = "https://example.com/images/photo%20name.jpg";
// ❌ 错误
const imageURL = "http://example.com/images/photo.jpg"; // 不允许 HTTP
const imageURL = "https://example.com/images/photo name.jpg"; // 空格未编码
始终对 URL 路径使用 encodeURIComponent():
const filename = "photo name.jpg";
const imageURL = `https://example.com/images/${encodeURIComponent(filename)}`;
错误 : Cf-Resized: err=9412 - 源站返回 HTML 而不是图像
发生原因 : 源服务器返回 404 页面或错误页面 (HTML) 而不是图像
预防措施 :
// 转换前验证 URL
const originResponse = await fetch(imageURL, { method: 'HEAD' });
const contentType = originResponse.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
return new Response('不是图像', { status: 400 });
}
return fetch(imageURL, { cf: { image: { width: 800 } } });
错误 : Cf-Resized: err=9413 - 图像超过 100 兆像素
发生原因 : 源图像尺寸超过 100 兆像素 (例如,10000x10000px)
预防措施 :
上传前验证图像尺寸
预处理过大的图像
拒绝超过阈值的图像
const MAX_MEGAPIXELS = 100;
if (width * height > MAX_MEGAPIXELS * 1_000_000) { return new Response('图像过大', { status: 413 }); }
错误 : 灵活变体不适用于私有图像
发生原因 : 灵活变体不能与 requireSignedURLs=true 一起使用
预防措施 :
// ✅ 正确 - 为私有图像使用命名变体
await uploadImage({
file: imageFile,
requireSignedURLs: true // 使用命名变体: /public, /avatar 等
});
// ❌ 错误 - 灵活变体不支持签名 URL
// 不能使用: /w=400,sharpen=3 配合 requireSignedURLs=true
错误 : SVG 文件无法通过转换调整大小
发生原因 : SVG 本质上是可缩放的 (矢量格式),调整大小不适用
预防措施 :
// SVG 可以被提供但无法调整大小
// 使用任何变体名称作为占位符
// https://imagedelivery.net/<HASH>/<SVG_ID>/public
// SVG 将以原始大小提供,无论变体设置如何
错误 : 上传的图像中移除了 GPS 数据、相机设置
发生原因 : 默认行为会剥离除版权信息外的所有元数据。关键点 : WebP 和 PNG 输出格式总是丢弃元数据,无论设置如何 - 只有 JPEG 支持保留元数据。
预防措施 :
// ✅ 正确 - JPEG 保留元数据
fetch(imageURL, {
cf: {
image: {
width: 800,
format: 'jpeg', // 或 'auto' (可能变为 jpeg)
metadata: 'keep' // 保留大多数 EXIF 元数据,包括 GPS
}
}
});
// ❌ 错误 - WebP/PNG 忽略元数据设置
fetch(imageURL, {
cf: {
image: {
format: 'webp',
metadata: 'keep' // 无效 - WebP/PNG 总是剥离元数据
}
}
});
元数据选项 (仅限 JPEG):
none: 剥离所有元数据copyright: 仅保留版权标签 (JPEG 的默认值)keep: 保留大多数 EXIF 元数据,包括 GPS格式支持 :
none)none)错误 : 大型 AVIF 转换失败或降级到较低分辨率
来源 : Cloudflare 文档 + 社区报告
发生原因 : 官方文档说明 format=avif 的硬限制为 1,600px,但社区报告指出,截至 2024 年底,最长边的实际限制为 1200px。注意 : 官方文档 (1600px) 与报告行为 (1200px) 之间的差异需要验证。
预防措施 :
// ✅ 推荐 - 使用 format=auto 而不是显式的 avif
fetch(imageURL, {
cf: {
image: {
width: 2000,
format: 'auto' // Cloudflare 选择最佳格式
}
}
});
// ⚠️ 可能失败 - 显式 AVIF 配合大尺寸
fetch(imageURL, {
cf: {
image: {
width: 2000,
format: 'avif' // 如果 >1200px 可能失败
}
}
});
// ✅ 变通方案 - 为较大图像使用 WebP
if (width > 1200) {
format = 'webp'; // WebP 支持更大尺寸
} else {
format = 'avif'; // 为较小图像使用 AVIF
}
错误 : 桌面端和移动端看到不同的缓存版本,意外的缓存未命中
来源 : Cloudflare 社区
发生原因 : 图像会按设备类型意外缓存,且在缓存规则中没有配置选项可以禁用此行为。截至 2024 年 12 月,多名用户确认了此行为。
预防措施 :
解决方案/变通方案 : 需要时通过 Cloudflare 仪表板按设备类型手动清除缓存。
错误 : PNG 图像不缓存,而其他图像格式正常工作
来源 : Cloudflare 社区
发生原因 : Cloudflare 默认缓存的文件扩展名不包含 .png。默认缓存的扩展名: .jpg, .jpeg, .gif, .webp, .bmp, .ico, .svg, .tif, .tiff (缺少 PNG)。
预防措施 : 为 PNG 文件创建显式缓存规则:
URI Path ends with .png → Cache Everything可直接复制粘贴的常见模式代码:
用法 :
cp templates/upload-api-basic.ts src/upload.ts
# 使用你的账户 ID 和 API 令牌进行编辑
Claude 可根据需要加载的深入文档:
何时加载 :
check-versions.sh - 验证 API 端点是否为最新
自定义域名 : 通过 /cdn-cgi/imagedelivery/<HASH>/<ID>/<VARIANT> 从你的域名提供服务 (要求域名在 Cloudflare 上,且已代理)。使用转换规则实现自定义路径。
批量 API : 通过 batch.imagedelivery.net 使用批量令牌进行高容量上传 (仪表板 → Images → Batch API)
Webhooks : 直接创作者上传的通知 (仪表板 → Notifications → Webhooks)。负载包含 imageId、status、metadata。
内容凭证 (C2PA) : Cloudflare Images 保留并签署图像来源链。当转换包含内容凭证的图像时,Cloudflare 会自动将转换附加到清单中,并使用 DigiCert 证书进行加密签名。通过仪表板 → Images → Transformations → Preserve Content Credentials 启用。注意 : 仅当源图像已包含 C2PA 元数据时有效 (某些相机、DALL-E、兼容的编辑软件)。请在 contentcredentials.org/verify 验证。
浏览器缓存 TTL : 默认浏览器缓存 TTL 为 2 天 (172,800 秒)。可从 1 小时到 1 年自定义 (账户级别或按变体)。重要 : 私有图像 (签名 URL) 不 遵守 TTL 设置。当使用相同的自定义 ID 重新上传图像时,可能会导致问题 - 用户可能最多看到旧版本 2 天。解决方案: 使用唯一的图像 ID (附加时间戳/哈希) 或在重新上传后手动清除缓存。
产品合并迁移 (2023年11月) : Cloudflare 将 Image Resizing 合并到 Images 产品中,采用新的定价: 每月每 1,000 次唯一转换 $0.50 (每 30 天对每个唯一转换计费一次)。迁移注意事项 : 旧的 Image Resizing 基于未缓存的请求计费 (由于缓存行为可变,成本不可预测)。新计费基于"唯一转换" (图像 ID + 参数组合),而不是请求次数。同一转换每月请求 1,000 次 = 计费一次。现有客户可以继续使用旧版本或自愿迁移。
用于成本优化的 R2 集成 : Cloudflare Images 基于 R2 + Image Resizing 构建。为节省成本,可将原始图像存储在 R2 中,并按需使用 Image Transformations:
/cdn-cgi/image/ 转换提供服务成本对比 :
使用场景 : 如果不需要命名变体、批量上传或直接创作者上传功能,R2 更具成本效益。
与 R2 配合使用的 Workers 模式示例 :
export default {
async fetch(request, env) {
const url = new URL(request.url);
const imageKey = url.pathname.replace('/images/', '');
const originURL = `https://r2-bucket.example.com/${imageKey}`;
return fetch(originURL, {
cf: {
image: {
width: 800,
quality: 85,
format: 'auto'
}
}
});
}
};
参考: Cloudflare 参考架构
症状 : /cdn-cgi/image/... 返回原始图像或 404
解决方案 :
症状 : 浏览器控制台中出现 Access-Control-Allow-Origin 错误
解决方案 :
multipart/form-data 编码 (让浏览器设置 Content-Type)/direct_upload;从后端调用file (而不是 image)症状 : 响应头中出现 Cf-Resized: err=9403
解决方案 :
症状 : 访问签名 URL 时出现 403 Forbidden
解决方案 :
requireSignedURLs=true 上传症状 : 上传返回 200 OK 但仪表板中未显示图像
解决方案 :
draft: true (直接创作者上传)/images/v1/{id} 检查)症状 : 即使用户从未访问过页面,重新上传后仍看到旧图像
解决方案 :
const imageId = \${baseId}-${Date.now()};根本原因 : Cloudflare 遵守源站的 Cache-Control 请求头。如果源站设置了较长的 max-age (例如 30 天),即使使用相同 ID 重新上传,图像仍保持缓存状态。
症状 : PNG 图像绕过缓存,而其他格式 (JPEG, WebP) 正确缓存
解决方案 :
URI Path ends with .png → Cache Everything.png (仅包含 .jpg, .jpeg, .gif, .webp, .bmp, .ico, .svg, .tif, .tiff)最后验证 : 2026-01-21 API 版本 : v2 (直接上传), v1 (标准上传) 可选 : @cloudflare/workers-types@4.20260108.0
技能版本 : 2.1.0 | 变更 : 新增 3 个问题 (AVIF 限制、按设备缓存、PNG 缓存),增强了元数据问题的格式细节,添加了内容凭证、浏览器缓存 TTL、产品合并说明、R2 集成模式、Mirage 弃用通知和缓存故障排除指南。错误数量: 13 → 16。
每周安装次数
328
仓库
GitHub 星标数
661
首次出现
2026年1月20日
安全审计
安装于
claude-code267
gemini-cli220
opencode216
cursor206
antigravity196
codex191
Status : Production Ready ✅ Last Updated : 2026-01-21 Dependencies : Cloudflare account with Images enabled Latest Versions : Cloudflare Images API v2, @cloudflare/workers-types@4.20260108.0
Recent Updates (2025) :
gravity=face with zoom control, GPU-based RetinaFace, 99.4% precision)Deprecation Notice : Mirage deprecated September 15, 2025. Migrate to Cloudflare Images for storage/transformations or use native <img loading="lazy"> for lazy loading.
Two features: Images API (upload/store with variants) and Image Transformations (resize any image via URL or Workers).
1. Enable : Dashboard → Images → Get Account ID + API token (Cloudflare Images: Edit permission)
2. Upload :
curl -X POST https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: multipart/form-data' \
-F 'file=@./image.jpg'
3. Serve : https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/public
4. Transform (optional): Dashboard → Images → Transformations → Enable for zone
<img src="/cdn-cgi/image/width=800,quality=85/uploads/photo.jpg" />
1. File Upload : POST to /images/v1 with file (multipart/form-data), optional id, requireSignedURLs, metadata
2. Upload via URL : POST with url=https://example.com/image.jpg (supports HTTP basic auth)
3. Direct Creator Upload (one-time URLs, no API key exposure):
Backend: POST to /images/v2/direct_upload → returns uploadURL Frontend: POST file to uploadURL with FormData
CRITICAL CORS FIX :
multipart/form-data (let browser set header)file (NOT image)/direct_upload from backend onlyContent-Type: application/json/direct_upload from browserURL : /cdn-cgi/image/<OPTIONS>/<SOURCE>
width=800,height=600,fit=coverquality=85 (1-100)format=auto (WebP/AVIF auto-detection)gravity=auto (smart crop), gravity=face (AI face detection, Aug 2025 GA), gravity=center, zoom=0.5 (0-1 range, face crop tightness)blur=10,sharpen=3,brightness=1.2scale-down, , , , Workers : Use cf.image object in fetch
fetch(imageURL, {
cf: {
image: { width: 800, quality: 85, format: 'auto', gravity: 'face', zoom: 0.8 }
}
});
Named Variants (up to 100): Predefined transformations (e.g., avatar, thumbnail)
/images/v1/variants with id, optionsimagedelivery.net/<HASH>/<ID>/avatarFlexible Variants : Dynamic params in URL (w=400,sharpen=3)
/images/v1/config with {"flexible_variants": true}Generate HMAC-SHA256 tokens for private images (URL format: ?exp=<TIMESTAMP>&sig=<HMAC>).
Algorithm : HMAC-SHA256(signingKey, imageId + variant + expiry) → hex signature
See : templates/signed-urls-generation.ts for Workers implementation
✅ Use multipart/form-data for Direct Creator Upload ✅ Name the file field file (not image or other names) ✅ Call /direct_upload API from backend only (NOT browser) ✅ Use HTTPS URLs for transformations (HTTP not supported) ✅ URL-encode special characters in image paths ✅ Enable transformations on zone before using /cdn-cgi/image/ ✅ Use named variants for private images (signed URLs) ✅ Check Cf-Resized header for transformation errors ✅ Set format=auto for automatic WebP/AVIF conversion ✅ Use fit=scale-down to prevent unwanted enlargement
❌ Use application/json Content-Type for file uploads ❌ Call /direct_upload from browser (CORS will fail) ❌ Use flexible variants with requireSignedURLs=true ❌ Resize SVG files (they're inherently scalable) ❌ Use HTTP URLs for transformations (HTTPS only) ❌ Put spaces or unescaped Unicode in URLs ❌ Transform the same image multiple times in Workers (causes 9403 loop) ❌ Exceed 100 megapixels image size ❌ Use /cdn-cgi/image/ endpoint in Workers (use cf.image instead) ❌ Forget to enable transformations on zone before use
This skill prevents 16 documented issues.
Error : Access to XMLHttpRequest blocked by CORS policy: Request header field content-type is not allowed
Source : Cloudflare Community #345739, #368114
Why It Happens : Server CORS settings only allow multipart/form-data for Content-Type header
Prevention :
// ✅ CORRECT
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch(uploadURL, {
method: 'POST',
body: formData // Browser sets multipart/form-data automatically
});
// ❌ WRONG
await fetch(uploadURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // CORS error
body: JSON.stringify({ file: base64Image })
});
Error : Error 5408 after ~15 seconds of upload
Source : Cloudflare Community #571336
Why It Happens : Cloudflare has 30-second request timeout; slow uploads or large files exceed limit
Prevention :
Compress images before upload (client-side with Canvas API)
Use reasonable file size limits (e.g., max 10MB)
Show upload progress to user
Handle timeout errors gracefully
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
if (file.size > MAX_FILE_SIZE) { alert('File too large. Please select an image under 10MB.'); return; }
Error : 400 Bad Request with unhelpful error message
Source : Cloudflare Community #487629
Why It Happens : File field must be named file (not image, photo, etc.)
Prevention :
// ✅ CORRECT
formData.append('file', imageFile);
// ❌ WRONG
formData.append('image', imageFile); // 400 error
formData.append('photo', imageFile); // 400 error
Error : Preflight OPTIONS request blocked
Source : Cloudflare Community #306805
Why It Happens : Calling /direct_upload API directly from browser (should be backend-only)
Prevention :
ARCHITECTURE:
Browser → Backend API → POST /direct_upload → Returns uploadURL → Browser uploads to uploadURL
Never expose API token to browser. Generate upload URL on backend, return to frontend.
Error : Cf-Resized: err=9401 - Required cf.image options missing or invalid
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Missing required transformation parameters or invalid values
Prevention :
// ✅ CORRECT
fetch(imageURL, {
cf: {
image: {
width: 800,
quality: 85,
format: 'auto'
}
}
});
// ❌ WRONG
fetch(imageURL, {
cf: {
image: {
width: 'large', // Must be number
quality: 150 // Max 100
}
}
});
Error : Cf-Resized: err=9402 - Image too large or connection interrupted
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Image exceeds maximum area (100 megapixels) or download fails
Prevention :
Error : Cf-Resized: err=9403 - Worker fetching its own URL or already-resized image
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Transformation applied to already-transformed image, or Worker fetches itself
Prevention :
// ✅ CORRECT
if (url.pathname.startsWith('/images/')) {
const originalPath = url.pathname.replace('/images/', '');
const originURL = `https://storage.example.com/${originalPath}`;
return fetch(originURL, { cf: { image: { width: 800 } } });
}
// ❌ WRONG
if (url.pathname.startsWith('/images/')) {
// Fetches worker's own URL, causes loop
return fetch(request, { cf: { image: { width: 800 } } });
}
Error : Cf-Resized: err=9406 or err=9419 - Non-HTTPS URL or URL has spaces/unescaped Unicode
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Image URL uses HTTP (not HTTPS) or contains invalid characters
Prevention :
// ✅ CORRECT
const imageURL = "https://example.com/images/photo%20name.jpg";
// ❌ WRONG
const imageURL = "http://example.com/images/photo.jpg"; // HTTP not allowed
const imageURL = "https://example.com/images/photo name.jpg"; // Space not encoded
Always use encodeURIComponent() for URL paths:
const filename = "photo name.jpg";
const imageURL = `https://example.com/images/${encodeURIComponent(filename)}`;
Error : Cf-Resized: err=9412 - Origin returned HTML instead of image
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Origin server returns 404 page or error page (HTML) instead of image
Prevention :
// Verify URL before transforming
const originResponse = await fetch(imageURL, { method: 'HEAD' });
const contentType = originResponse.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
return new Response('Not an image', { status: 400 });
}
return fetch(imageURL, { cf: { image: { width: 800 } } });
Error : Cf-Resized: err=9413 - Image exceeds 100 megapixels
Source : Cloudflare Images Docs - Troubleshooting
Why It Happens : Source image dimensions exceed 100 megapixels (e.g., 10000x10000px)
Prevention :
Validate image dimensions before upload
Pre-process oversized images
Reject images above threshold
const MAX_MEGAPIXELS = 100;
if (width * height > MAX_MEGAPIXELS * 1_000_000) { return new Response('Image too large', { status: 413 }); }
Error : Flexible variants don't work with private images
Source : Cloudflare Images Docs - Enable flexible variants
Why It Happens : Flexible variants cannot be used with requireSignedURLs=true
Prevention :
// ✅ CORRECT - Use named variants for private images
await uploadImage({
file: imageFile,
requireSignedURLs: true // Use named variants: /public, /avatar, etc.
});
// ❌ WRONG - Flexible variants don't support signed URLs
// Cannot use: /w=400,sharpen=3 with requireSignedURLs=true
Error : SVG files don't resize via transformations
Source : Cloudflare Images Docs - SVG files
Why It Happens : SVG is inherently scalable (vector format), resizing not applicable
Prevention :
// SVGs can be served but not resized
// Use any variant name as placeholder
// https://imagedelivery.net/<HASH>/<SVG_ID>/public
// SVG will be served at original size regardless of variant settings
Error : GPS data, camera settings removed from uploaded images
Source : Cloudflare Images Docs - Transform via URL
Why It Happens : Default behavior strips all metadata except copyright. CRITICAL : WebP and PNG output formats ALWAYS discard metadata regardless of settings - only JPEG supports metadata preservation.
Prevention :
// ✅ CORRECT - JPEG preserves metadata
fetch(imageURL, {
cf: {
image: {
width: 800,
format: 'jpeg', // or 'auto' (may become jpeg)
metadata: 'keep' // Preserves most EXIF including GPS
}
}
});
// ❌ WRONG - WebP/PNG ignore metadata setting
fetch(imageURL, {
cf: {
image: {
format: 'webp',
metadata: 'keep' // NO EFFECT - always stripped for WebP/PNG
}
}
});
Metadata Options (JPEG only):
none: Strip all metadatacopyright: Keep only copyright tag (default for JPEG)keep: Preserve most EXIF metadata including GPSFormat Support :
none)none)Error : Large AVIF transformations fail or degrade to lower resolution
Source : Cloudflare Docs + Community Report
Why It Happens : Official docs state 1,600px hard limit for format=avif, but community reports indicate practical limit of 1200px for longest side as of late 2024. Note : Discrepancy between official docs (1600px) and reported behavior (1200px) needs verification.
Prevention :
// ✅ RECOMMENDED - Use format=auto instead of explicit avif
fetch(imageURL, {
cf: {
image: {
width: 2000,
format: 'auto' // Cloudflare chooses best format
}
}
});
// ⚠️ MAY FAIL - Explicit AVIF with large dimensions
fetch(imageURL, {
cf: {
image: {
width: 2000,
format: 'avif' // May fail if >1200px
}
}
});
// ✅ WORKAROUND - Use WebP for larger images
if (width > 1200) {
format = 'webp'; // WebP supports larger dimensions
} else {
format = 'avif'; // AVIF for smaller images
}
Error : Desktop and mobile see different cached versions, unexpected cache misses
Source : Cloudflare Community
Why It Happens : Images are cached by device type unexpectedly with no configuration option to disable this behavior in cache rules. Multiple users confirm this behavior as of Dec 2024.
Prevention :
Solution/Workaround : Manual cache purging per device type via Cloudflare dashboard when needed.
Error : PNG images not caching despite other image formats working
Source : Cloudflare Community
Why It Happens : Default file extensions cached by Cloudflare do NOT include .png. Default cached extensions: .jpg, .jpeg, .gif, .webp, .bmp, .ico, .svg, .tif, .tiff (PNG is missing).
Prevention : Create explicit cache rule for PNG files:
URI Path ends with .png → Cache EverythingCopy-paste ready code for common patterns:
Usage :
cp templates/upload-api-basic.ts src/upload.ts
# Edit with your account ID and API token
In-depth documentation Claude can load as needed:
When to load :
check-versions.sh - Verify API endpoints are current
Custom Domains : Serve from your domain via /cdn-cgi/imagedelivery/<HASH>/<ID>/<VARIANT> (requires domain on Cloudflare, proxied). Use Transform Rules for custom paths.
Batch API : High-volume uploads via batch.imagedelivery.net with batch tokens (Dashboard → Images → Batch API)
Webhooks : Notifications for Direct Creator Upload (Dashboard → Notifications → Webhooks). Payload includes imageId, status, metadata.
Content Credentials (C2PA) : Cloudflare Images preserves and signs image provenance chains. When transforming images with Content Credentials, Cloudflare automatically appends transformations to the manifest and cryptographically signs them using DigiCert certificates. Enable via Dashboard → Images → Transformations → Preserve Content Credentials. Note : Only works if source images already contain C2PA metadata (certain cameras, DALL-E, compatible editing software). Verify at contentcredentials.org/verify.
Browser Cache TTL : Default Browser Cache TTL is 2 days (172,800 seconds). Customizable from 1 hour to 1 year (account-level or per-variant). Important : Private images (signed URLs) do NOT respect TTL settings. Can cause issues when re-uploading images with same Custom ID - users may see old version for up to 2 days. Solution: Use unique image IDs (append timestamp/hash) or manually purge cache after re-upload.
Product Merge Migration (Nov 2023) : Cloudflare merged Image Resizing into Images product with new pricing: $0.50 per 1,000 unique transformations monthly (billing once per 30 days per unique transformation). Migration Gotchas : Old Image Resizing bills based on uncached requests (unpredictable costs due to variable cache behavior). New billing is per "unique transformation" (image ID + params combination), not request count. Same transformation requested 1,000 times/month = billed once. Existing customers can continue using legacy version or migrate voluntarily.
R2 Integration for Cost Optimization : Cloudflare Images is built on R2 + Image Resizing. For cost savings, store original images in R2 and use Image Transformations on-demand:
/cdn-cgi/image/ transformationsCost Comparison :
Use Case : If you don't need named variants, batch uploads, or Direct Creator Upload features, R2 is more cost-effective.
Example Workers Pattern with R2 :
export default {
async fetch(request, env) {
const url = new URL(request.url);
const imageKey = url.pathname.replace('/images/', '');
const originURL = `https://r2-bucket.example.com/${imageKey}`;
return fetch(originURL, {
cf: {
image: {
width: 800,
quality: 85,
format: 'auto'
}
}
});
}
};
Reference: Cloudflare Reference Architecture
Symptoms : /cdn-cgi/image/... returns original image or 404
Solutions :
Symptoms : Access-Control-Allow-Origin error in browser console
Solutions :
multipart/form-data encoding (let browser set Content-Type)/direct_upload from browser; call from backendfile (not image)Symptoms : Cf-Resized: err=9403 in response headers
Solutions :
Symptoms : 403 Forbidden when accessing signed URL
Solutions :
requireSignedURLs=trueSymptoms : Upload returns 200 OK but image not in dashboard
Solutions :
draft: true in response (Direct Creator Upload)/images/v1/{id})Symptoms : Even users who never visited page see old image after re-upload
Solutions :
const imageId = \${baseId}-${Date.now()};Root Cause : Cloudflare respects origin Cache-Control headers. If origin sets long max-age (e.g., 30 days), images remain cached even after re-upload with same ID.
Symptoms : PNG images bypass cache while other formats (JPEG, WebP) cache correctly
Solutions :
URI Path ends with .png → Cache Everything.png (only .jpg, .jpeg, .gif, .webp, .bmp, .ico, .svg, , )Last Verified : 2026-01-21 API Version : v2 (direct uploads), v1 (standard uploads) Optional : @cloudflare/workers-types@4.20260108.0
Skill Version : 2.1.0 | Changes : Added 3 new issues (AVIF limits, cache by device, PNG caching), enhanced metadata issue with format details, added Content Credentials, Browser Cache TTL, Product Merge notes, R2 integration pattern, Mirage deprecation notice, and cache troubleshooting guidance. Error count: 13 → 16.
Weekly Installs
328
Repository
GitHub Stars
661
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykFail
Installed on
claude-code267
gemini-cli220
opencode216
cursor206
antigravity196
codex191
Azure 升级评估与自动化工具 - 轻松迁移 Functions 计划、托管层级和 SKU
66,100 周安装
Notion规范转实现计划工具:AI驱动项目管理,自动生成任务与跟踪进度
495 周安装
React Native 测试模式与工具:TDD、工厂模式、模拟模块实战指南
472 周安装
Tailwind v4 + shadcn/ui 生产级技术栈配置指南:5分钟快速搭建,避免常见错误
511 周安装
批判性思维与逻辑推理指南 - 提升AI智能体分析能力,避免信号稀释与语境坍塌
533 周安装
Three.js 3D Web开发教程 - WebGL/WebGPU图形编程、动画与性能优化指南
484 周安装
用户故事拆分指南:8种模式分解大型故事,提升敏捷开发效率
518 周安装
containcovercroppad.tif.tiff