在使用 opennextjs-cloudflare 部署 Next.js 时,图片优化是一个大坑。

最初我尝试了官方的 Binding 模式,但遇到了格式无法自动转换(一直是 JPEG)和缓存配置麻烦的问题。经过摸索,最终切换到了 Custom Loader 模式。这个方案直接利用 Cloudflare 原生的图片转换服务(/cdn-cgi/image/),不仅无需消耗 Worker 资源,还能强制输出 WebP/AVIF 格式,速度飞快。

1. 开启 Cloudflare 服务 (省钱基础)

首先确保 Cloudflare 账户开启了图片服务。

  1. 进入 CF 后台 -> Media -> Images
  2. 关键点:选择 “Use my own storage” 方案。
  3. 费用:$0/月 (包含每月 5,000 次免费变换)。
  4. 额外检查:去 Speed -> Optimization -> Image Optimization,确保它是 On 的状态(这激活了 Zone 级别的图片处理能力)。

2. 代码配置 (核心步骤)

我们不再依赖 Worker 的后台绑定,而是告诉 Next.js:“请生成 Cloudflare 能看懂的 URL”。

第一步:创建 Loader 文件

在项目根目录(和 next.config.mjs 同级)新建文件 cf-image-loader.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// cf-image-loader.js
const normalizeSrc = (src) => {
  return src.startsWith("/") ? src.slice(1) : src;
};

export default function cloudflareLoader({ src, width, quality }) {
  // 核心魔法:format=auto 让 CF 自动根据浏览器支持返回 WebP 或 AVIF
  const params = [`width=${width}`, `quality=${quality || 75}`, "format=auto"];

  // 生成标准的 CF 图片 URL: /cdn-cgi/image/...
  return `/cdn-cgi/image/${params.join(",")}/${normalizeSrc(src)}`;
}

第二步:修改 Next.js 配置

修改 next.config.mjs,启用自定义 Loader:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    // 1. 启用自定义 loader
    loader: "custom",
    loaderFile: "./cf-image-loader.js",

    // 2. 依然保留尺寸限制 (为了省钱,防止生成过多变体)
    deviceSizes: [640, 1080, 1920],
    imageSizes: [32, 64],

    // 3. 远程图片白名单 (Next.js 组件层面的校验)
    remotePatterns: [
      {
        protocol: "https",
        hostname: "example.com",
      },
    ],
  },
};
export default nextConfig;

3. 环境与域名 (避坑指南)

这是最大的坑!Custom Loader 方案不支持 workers.dev 域名。

  • ❌ 错误做法:在 xxx.workers.dev 上测试。你会发现图片全部 404。
    • 原因:Cloudflare 禁止在共享域名上使用 /cdn-cgi/ 功能。
  • ✅ 正确做法:必须绑定自定义域名(如 example.compreview.example.com)。

操作

  1. 在 Cloudflare 后台给 Worker 绑定一个域名(Triggers -> Custom Domains)。
  2. 确保你在浏览器访问的是这个自定义域名,图片才能正常显示。

4. 权限白名单 (Sources)

如果你要加载远程图片(如 AWS S3),必须在 Cloudflare 后台放行,否则会报 403。

  1. 进入 CF 后台 -> Images -> Overview
  2. 找到 Sources (Configuration)。
  3. 点击 Add origin,把你图片所在的域名加进去。
    • 如果是本地图片 (public文件夹),默认允许当前域名,无需配置。

5. 开发中的写法

配置好 Loader 后,React 代码里的写法完全不变,Next.js 会自动调用 Loader 生成新 URL。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Image from 'next/image';
import localPic from '../public/hero.jpg';

// 场景 A: 本地图片
<Image src={localPic} alt="Hero" />

// 场景 B: 远程图片 (记得填宽高)
<Image
  src="https://example.com/pic.jpg"
  width={800}
  height={600}
  alt="Remote"
/>

// 场景 C: 自适应填充 (配合 Tailwind)
<div className="relative h-64 w-full">
  <Image
    src="/bg.jpg"
    alt="Cover"
    fill
    className="object-cover"
    sizes="(max-width: 768px) 100vw, 50vw"
  />
</div>

6. 如何验证成功?

部署后(记得部署到自定义域名环境),按 F12 检查图片:

  1. 看 URL:地址应该是 /cdn-cgi/image/width=...,format=auto/...
  2. 看格式:Network 面板里,Content-Type 应该是 image/webpimage/avif(体积显著变小)。
  3. 看缓存:Response Headers 里会有 CF-Cache-Status: HIT(自带 CDN 缓存,无需额外配置)。

附录:关于构建报错 如果在部署预览环境时遇到 The entry-point file at ".open-next\worker.js" was not found,说明你还没编译。 请先运行构建命令,再部署:

1
2
3
4
# 先构建
npx opennextjs-cloudflare build
# 再部署到预览环境
npx wrangler deploy --env preview