11. 重构为worker
会员专享 · 非会员仅可阅读 30% 的正文。
- 发布时间
- May 17, 2025
- 阅读时间
- 7 min read
- 作者
- Felix
- 访问
- 会员专享
非会员仅可阅读 30% 的正文。
基础配置流程
将Next.js应用重构为Cloudflare Worker需要一些下面的配置和操作,但其实代码已经写好了在Tag里,只是讲一下过程。
下载依赖、修改配置文件、添加脚本
首先执行pnpm add @opennextjs/cloudflare,紧接着完成wrangler.jsonc的配置,这部分配置会在上传到Cloudflare的时候自动完成绑定。
重构的配置是这些:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "cloudflare-template",
"main": ".open-next/worker.js",
"compatibility_date": "2025-04-22",
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"observability": {
"logs": {
"enabled": true
}
},
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"ai": {
"binding": "AI"
},
"r2_buckets": [
{
"bucket_name": "demo",
"binding": "NEXT_INC_CACHE_R2_BUCKET"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "cloudflare-template"
}
]
}
-
$schema: 指向配置文件的JSON Schema,提供编辑器自动完成功能。 -
name: Worker的名称,这将是你的 Worker 在 Cloudflare 控制台中显示的名称,也是访问 Worker 时使用的子域名的一部分。 -
main: Worker的入口文件,对于 OpenNext 项目,这通常是构建后生成的 .open-next/worker.js 文件。 -
compatibility_date: 指定Worker运行时版本的日期,Cloudflare 会根据这个日期确定使用哪个版本的 Workers 运行时。 -
compatibility_flags: 启用Node.js兼容层和公共URL请求功能 -
observability: 启用日志记录,方便在Cloudflare控制台查看运行日志 -
assets: 配置静态资源directory: 静态资源目录binding: 在Worker中访问资源的变量名,也是我们在环境中写的变量名。
-
ai: 绑定Cloudflare AI服务,可用于文本生成、图像处理等AI功能 -
r2_buckets: 配置R2存储桶bucket_name: 存储桶名称binding: 创建一个名为NEXT_INC_CACHE_R2_BUCKET的绑定,用于 Next.js 的增量缓存
-
durable_objects: 配置有状态存储服务NEXT_CACHE_DO_QUEUE: 处理Next.js缓存队列NEXT_TAG_CACHE_DO_SHARDED: 处理Next.js标签缓存
-
migrations: 为Durable Objects创建SQLite数据库 -
services: 这个配置允许 Worker 引用自己,这在某些需要 Worker 调用自身的场景中很有用,比如实现某些递归操作或分布式处理。
在package.json中添加以下脚本,简化开发和部署流程:
"scripts": {
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"upload": "opennextjs-cloudflare build && opennextjs-cloudflare upload",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
}
这些命令分别用于:
- preview: 构建应用并在本地Workers运行时预览,让你通过单个命令快速预览应用
- deploy: 构建并立即部署到Cloudflare
- upload: 构建并上传新版本到Cloudflare
- cf-typegen: 在项目根目录生成包含env类型的cloudflare-env.d.ts文件
紧接着跑一下pnpm cf-typegen自动生成项目的类型,这个命令在每次新增绑定的服务的时候都可以调用一下,用于生成类型,便于语法提示。
配置文件设置
虽然在构建过程中@opennextjs/cloudflare会自动创建配置文件(如果不存在),但你也可以自己创建。除了上面的wrangler.jsonc配置外,还需要创建一个open-next.config.ts文件:
import { defineCloudflareConfig } from '@opennextjs/cloudflare'
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache'
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue'
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'
export default defineCloudflareConfig({
incrementalCache: r2IncrementalCache,
tagCache: doShardedTagCache({ baseShardSize: 12, regionalCache: true }),
queue: doQueue
})
这个配置是启用了Cloudflare的缓存以支持Nextjs的缓存功能。
静态资源缓存优化
创建public/_headers文件,添加缓存控制:
/_next/static/*
Cache-Control: public,max-age=31536000,immutable
这能确保静态资源得到适当缓存,提高加载速度。更多信息可以参考静态资源缓存文档。
修改Next.js配置
更新next.config.ts文件,添加Cloudflare开发集成:
import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare'
import { NextConfig } from 'next'
import createNextIntlPlugin from 'next-intl/plugin'
initOpenNextCloudflareForDev()
const nextConfig: NextConfig = {
devIndicators: false,
experimental: {
staleTimes: {
dynamic: 3600,
static: 3600
}
}
}
const withNextIntl = createNextIntlPlugin({
experimental: {
createMessagesDeclaration: './messages/en.json'
}
})
export default withNextIntl(nextConfig)
添加initOpenNextCloudflareForDev()调用后,你可以在本地开发过程中访问Cloudflare绑定的本地版本,如绑定文档中所述。
我们在第3步中还添加了npm run preview命令,它允许你在Workers运行时而不是Node.js中快速预览应用。这使你能够在与部署到Cloudflare时相同的运行时中测试更改。
替换移除代码
- 移除任何
export const runtime = "edge";声明,因为@opennextjs/cloudflare不支持edge运行时。 - 移除
@cloudflare/next-on-pages这个包。 - 用
@opennextjs/cloudflare中的getCloudflareContext替换@cloudflare/next-on-pages中的getRequestContext
部署
- 通过Push,CL会自己完成部署。
pnpm deploy:c会读取本地的.env文件进行部署。
Cloudflare如何实现Nextjs的缓存
接下来讲一下原理源码,这部分的内容是属于可看可不看的,这是满足我对技术的好奇心以及对使用一门新技术的安全感。
Next.js提供了多种通过缓存路由和网络请求来提高应用性能的方法。应用程序会尝试在构建时预渲染并缓存尽可能多的数据,以减少向用户提供响应时所需的工作量。
缓存数据通过重新验证进行更新,可以是定期的或按需的:
- 基于时间的重新验证:在应用程序指定的重新验证延迟过期后更新缓存数据
- 按需重新验证:允许通过特定标签(通过
revalidateTag)或在给定路径(通过revalidatePath)使缓存条目无效。
@opennextjs/cloudflare的缓存支持依赖于3个组件:
- 增量缓存(Incremental Cache):存储缓存数据
- 队列(Queue):同步和去重基于时间的重新验证
- 标签缓存(Tag Cache):用于通过
revalidateTag和revalidatePath进行按需重新验证
缓存实现
而对于这三者的实现我采用的是:
R2增量缓存实现原理
R2增量缓存是Next.js在Cloudflare环境中存储缓存数据的主要机制。它利用Cloudflare的R2对象存储服务,提供高性能、低成本的数据存储解决方案。
R2增量缓存实现了标准的Next.js增量缓存接口,包括get、set和delete方法,使其能够无缝集成到Next.js的缓存系统中。
核心实现代码如下:
class R2IncrementalCache implements IncrementalCache {
readonly name = NAME
async get<CacheType extends CacheEntryType = 'cache'>(
key: string,
cacheType?: CacheType
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
const r2 = getCloudflareContext().env[BINDING_NAME]
if (!r2) throw new IgnorableError('No R2 bucket')
debugCache(`Get ${key}`)
try {
const r2Object = await r2.get(this.getR2Key(key, cacheType))
if (!r2Object) return null
return {
value: await r2Object.json(),
lastModified: r2Object.uploaded.getTime()
}
} catch (e) {
error('Failed to get from cache', e)
return null
}
}
async set<CacheType extends CacheEntryType = 'cache'>(
key: string,
value: CacheValue<CacheType>,
cacheType?: CacheType
): Promise<void> {
const r2 = getCloudflareContext().env[BINDING_NAME]
if (!r2) throw new IgnorableError('No R2 bucket')
debugCache(`Set ${key}`)
try {
await r2.put(this.getR2Key(key, cacheType), JSON.stringify(value))
} catch (e) {
error('Failed to set to cache', e)
}
}
async delete(key: string): Promise<void> {
const r2 = getCloudflareContext().env[BINDING_NAME]
if (!r2) throw new IgnorableError('No R2 bucket')
debugCache(`Delete ${key}`)
try {
await r2.delete(this.getR2Key(key))
} catch (e) {
error('Failed to delete from cache', e)
}
}
protected getR2Key(key: string, cacheType?: CacheEntryType): string {
return computeCacheKey(key, {
prefix: getCloudflareContext().env[PREFIX_ENV_NAME],
buildId: process.env.NEXT_BUILD_ID,
cacheType
})
}
}
订阅后解锁完整文章
支持创作、解锁全文,未来更新也会第一时间送达。
评论
加入讨论
还没有评论,来占个沙发吧。