在使用 opennextjs-cloudflare 部署 Next.js 时,图片优化是一个大坑。
最初我尝试了官方的 Binding 模式,但遇到了格式无法自动转换(一直是 JPEG)和缓存配置麻烦的问题。经过摸索,最终切换到了 Custom Loader 模式。这个方案直接利用 Cloudflare 原生的图片转换服务(/cdn-cgi/image/),不仅无需消耗 Worker 资源,还能强制输出 WebP/AVIF 格式,速度飞快。
1. 开启 Cloudflare 服务 (省钱基础)
首先确保 Cloudflare 账户开启了图片服务。
- 进入
CF 后台->Media->Images。 - 关键点:选择 “Use my own storage” 方案。
- 费用:$0/月 (包含每月 5,000 次免费变换)。
- 额外检查:去 Speed -> Optimization -> Image Optimization,确保它是 On 的状态(这激活了 Zone 级别的图片处理能力)。
2. 代码配置 (核心步骤)
我们不再依赖 Worker 的后台绑定,而是告诉 Next.js:“请生成 Cloudflare 能看懂的 URL”。
第一步:创建 Loader 文件
在项目根目录(和 next.config.mjs 同级)新建文件 cf-image-loader.js:
1// cf-image-loader.js
2const normalizeSrc = (src) => {
3 return src.startsWith("/") ? src.slice(1) : src;
4};
5
6export default function cloudflareLoader({ src, width, quality }) {
7 // 核心魔法:format=auto 让 CF 自动根据浏览器支持返回 WebP 或 AVIF
8 const params = [`width=${width}`, `quality=${quality || 75}`, "format=auto"];
9
10 // 生成标准的 CF 图片 URL: /cdn-cgi/image/...
11 return `/cdn-cgi/image/${params.join(",")}/${normalizeSrc(src)}`;
12}
第二步:修改 Next.js 配置
修改 next.config.mjs,启用自定义 Loader:
1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 images: {
4 // 1. 启用自定义 loader
5 loader: "custom",
6 loaderFile: "./cf-image-loader.js",
7
8 // 2. 依然保留尺寸限制 (为了省钱,防止生成过多变体)
9 deviceSizes: [640, 1080, 1920],
10 imageSizes: [32, 64],
11
12 // 3. 远程图片白名单 (Next.js 组件层面的校验)
13 remotePatterns: [
14 {
15 protocol: "https",
16 hostname: "example.com",
17 },
18 ],
19 },
20};
21export default nextConfig;
3. 环境与域名 (避坑指南)
这是最大的坑!Custom Loader 方案不支持 workers.dev 域名。
- ❌ 错误做法:在
xxx.workers.dev上测试。你会发现图片全部 404。- 原因:Cloudflare 禁止在共享域名上使用
/cdn-cgi/功能。
- 原因:Cloudflare 禁止在共享域名上使用
- ✅ 正确做法:必须绑定自定义域名(如
example.com或preview.example.com)。
操作:
- 在 Cloudflare 后台给 Worker 绑定一个域名(Triggers -> Custom Domains)。
- 确保你在浏览器访问的是这个自定义域名,图片才能正常显示。
4. 权限白名单 (Sources)
如果你要加载远程图片(如 AWS S3),必须在 Cloudflare 后台放行,否则会报 403。
- 进入 CF 后台 -> Images -> Overview。
- 找到 Sources (Configuration)。
- 点击 Add origin,把你图片所在的域名加进去。
- 如果是本地图片 (public文件夹),默认允许当前域名,无需配置。
5. 开发中的写法
配置好 Loader 后,React 代码里的写法完全不变,Next.js 会自动调用 Loader 生成新 URL。
1import Image from 'next/image';
2import localPic from '../public/hero.jpg';
3
4// 场景 A: 本地图片
5<Image src={localPic} alt="Hero" />
6
7// 场景 B: 远程图片 (记得填宽高)
8<Image
9 src="https://example.com/pic.jpg"
10 width={800}
11 height={600}
12 alt="Remote"
13/>
14
15// 场景 C: 自适应填充 (配合 Tailwind)
16<div className="relative h-64 w-full">
17 <Image
18 src="/bg.jpg"
19 alt="Cover"
20 fill
21 className="object-cover"
22 sizes="(max-width: 768px) 100vw, 50vw"
23 />
24</div>
6. 如何验证成功?
部署后(记得部署到自定义域名环境),按 F12 检查图片:
- 看 URL:地址应该是
/cdn-cgi/image/width=...,format=auto/...。 - 看格式:Network 面板里,
Content-Type应该是image/webp或image/avif(体积显著变小)。 - 看缓存:Response Headers 里会有
CF-Cache-Status: HIT(自带 CDN 缓存,无需额外配置)。
附录:关于构建报错
如果在部署预览环境时遇到 The entry-point file at ".open-next\worker.js" was not found,说明你还没编译。
请先运行构建命令,再部署:
1# 先构建
2npx opennextjs-cloudflare build
3# 再部署到预览环境
4npx wrangler deploy --env preview