将 Next.js 应用从 Vercel 迁移到 Cloudflare Workers
快速摘要
一篇实战指南,记录如何使用 OpenNext 将 Next.js 16 应用从 Vercel 迁移到 Cloudflare Workers,涵盖配置、踩坑和经验总结。 文章会结合 Next.js、Cloudflare、OpenNext、部署 讲清核心概念、实际取舍和适用场景,方便你先快速把握重点,再决定是否阅读全文。
主题:Next.js、Cloudflare、OpenNext、部署

Vercel 是一个优秀的 Next.js 部署平台,但 Cloudflare Workers 有其独特优势:
问题在于:Next.js 基于 Node.js 构建,而 Cloudflare Workers 运行在 V8 隔离运行时(workerd)上。这就是 OpenNext 的用武之地。
OpenNext 是一个开源适配器,让 Next.js 可以跨平台部署。@opennextjs/cloudflare 包负责将 Next.js 功能转换为 Workers 兼容的代码。
它支持大部分核心 Next.js 功能,包括 App Router、Server Components、Route Handlers、图片优化、ISR 和 SSG。中间件也支持,但 Next.js 16 中的 Node 中间件(即 proxy.ts)目前还不受 OpenNext Cloudflare 支持。
bun add @opennextjs/cloudflare
bun add -d wrangler
wrangler.jsonc这是 Cloudflare Workers 的配置文件,放在项目根目录:
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "my-app",
"compatibility_date": "2026-04-06",
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "my-app"
}
],
"images": {
"binding": "IMAGES"
}
}
关键配置说明:
nodejs_compat — Next.js 在 Workers 上运行的必要条件,启用 Node.js API 兼容。global_fetch_strictly_public — 允许 fetch() 调用公网 URL。WORKER_SELF_REFERENCE — service 名称必须与 name 字段一致。images 绑定 — 启用 Cloudflare 图片优化。open-next.config.tsimport { defineCloudflareConfig } from '@opennextjs/cloudflare'
import staticAssetsIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache'
export default defineCloudflareConfig({
incrementalCache: staticAssetsIncrementalCache,
enableCacheInterception: true,
})
如果需要完整的 ISR / 按需重验证功能,可以将 staticAssetsIncrementalCache 替换为 r2IncrementalCache 并配置 R2 存储桶。对于以静态内容为主的站点,静态资源缓存更简单也足够用。
OpenNext 支持在 Cloudflare 上进行图片优化,而 wrangler.jsonc 里的 images binding 是关键配置。在我的项目里,我选择了自定义 Next.js image loader,让 next/image 的请求走 Cloudflare 的图片优化管道:
import type { ImageLoaderProps } from 'next/image'
function normalizeSrc(src: string) {
return src.startsWith('/') ? src.slice(1) : src
}
export default function cloudflareLoader({
src,
width,
quality,
}: ImageLoaderProps) {
const params = [`width=${width}`]
if (quality) {
params.push(`quality=${quality}`)
}
if (process.env.NODE_ENV === 'development') {
return `${src}?${params.join('&')}`
}
return `/cdn-cgi/image/${params.join(',')}/${normalizeSrc(src)}`
}
然后在 next.config.ts 中使用它:
const nextConfig: NextConfig = {
images: {
loader: 'custom',
loaderFile: './image-loader.ts',
},
// ...
}
这是一种实用的配置方式,但不是唯一方案。真正重要的是确保你的部署已经正确配置,让 OpenNext 和 Cloudflare 可以协同处理图片优化。如果你的项目已经有一套可用的图片策略,也不一定非要引入自定义 loader。
next.config.ts 以支持本地开发在配置文件中调用 initOpenNextCloudflareForDev(),用于在本地开发时注入 Cloudflare 绑定:
import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare'
import type { NextConfig } from 'next'
initOpenNextCloudflareForDev()
const nextConfig: NextConfig = {
// ... 你的配置
}
export default nextConfig
创建 public/_headers 为静态资源设置缓存策略:
/_next/static/*
Cache-Control: public,max-age=31536000,immutable
package.json 脚本{
"scripts": {
"dev": "next dev",
"build": "next build",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"
}
}
.gitignore# cloudflare
.open-next
.wrangler
bun run deploy
就这样,你的应用现在运行在 Cloudflare Workers 上了。
fs 模块在运行时不可用这是最大的坑。Cloudflare Workers 没有传统文件系统。如果你的应用在请求处理阶段读取 node:fs,运行时就会失败,即使同一段代码在 Vercel 或本地 Node.js 开发环境里可以正常工作。
我的案例中,RSS 路由在每次请求时动态读取 MDX 文件:
// 这在 Vercel(Node.js 运行时)上正常工作,但在 Workers 上会失败
const files = await fs.readdir(
path.join(process.cwd(), 'src', 'content', locale)
)
在我的案例里,解决方案是让路由在构建时静态生成,这样 fs 操作只会发生在构建阶段:
import { routing } from '@/i18n/routing'
export const dynamic = 'force-static'
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }))
}
这并不意味着所有场景都必须使用 generateStaticParams 和 force-static。更准确的经验法则是:在 Cloudflare Workers 上,避免在请求处理阶段读取本地文件系统。如果某个路由依赖仓库中的文件,可以将这部分工作前移到构建阶段、在合适的情况下改为静态路由,或者把内容迁移到 R2、KV、D1 或外部 CMS 这类运行时可访问的存储服务。
proxy.ts)暂不受支持OpenNext Cloudflare 本身是基于 Workers 的 Node.js 兼容层来运行 Next.js 应用的,所以应用整体并不需要被限制在 Edge runtime 上。但 Next.js 的 Node 中间件目前还不受支持;如果你使用了 proxy.ts,OpenNext 会在构建阶段直接报错:
ERROR Node.js middleware is not currently supported. Consider switching to Edge Middleware.
在 Next.js 16 中,proxy.ts 取代了 middleware.ts,并且默认运行在 Node.js runtime 上;不兼容的正是这一点。如果你现有的逻辑本身可以作为 Edge 兼容中间件运行,那么继续保留 middleware.ts,或者改回 middleware.ts,通常是一个可行的兼容方案。但如果这段逻辑依赖 Node-only API,光改文件名并不够,仍然需要把逻辑重写为 Edge 兼容版本,或者迁移到别的层处理。
bun.lockb 可能导致构建失败如果使用 Cloudflare 的 Git 集成进行自动部署,构建环境可能使用与本地不同版本的 Bun。二进制 bun.lockb 格式在 Bun 大版本之间不向后兼容,会导致类似这样的错误:
Outdated lockfile version: failed to parse lockfile: 'bun.lockb'
error: lockfile had changes, but lockfile is frozen
解决方案:切换到文本格式的 bun.lock:
bun install --save-text-lockfile
Cloudflare 的 Worker 大小限制看的是压缩后的上传体积,而不是原始 bundle 大小。使用 OpenNext 构建或部署时,Wrangler 会同时输出这两个数字:
Total Upload: 13833.20 KiB / gzip: 2295.89 KiB
真正决定是否超出 Cloudflare 限制的是后面的 gzip 大小。对于 Next.js + OpenNext 项目来说,这一点很容易被忽略,但在判断 Free/付费计划是否够用时非常关键。
别忘了清理 Vercel 特有的依赖:
@vercel/analytics@vercel/speed-insightsvercel.json替换为 Google Analytics、Cloudflare Web Analytics 或其他分析工具。
如果你的域名已经在 Cloudflare 上(使用 Workers 的话大概率已经是了),将它指向你的 Worker:
www 重定向到裸域名,添加一条 www 的 CNAME 记录指向裸域名(开启代理),然后使用 Cloudflare 的 重定向规则 中的「从 WWW 重定向到根」模板创建规则。从 Vercel 迁移到 Cloudflare Workers 的过程,得益于 OpenNext 的存在,出奇地顺畅。主要需要注意的是运行时文件系统访问和中间件兼容性。解决这些问题后,一切正常工作。
Cloudflare 的生态系统很有吸引力 — 图片优化、边缘缓存、DDoS 防护和 Workers 紧密集成。对于个人站点或博客,免费额度绰绰有余。