通用基础项目

    11. 重构为worker

    会员专享 · 非会员仅可阅读 30% 的正文。

    发布时间
    May 17, 2025
    阅读时间
    7 min read
    作者
    Felix
    访问
    会员专享
    这是预览内容

    非会员仅可阅读 30% 的正文。

    章节Tag

    基础配置流程

    将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个组件:

    1. 增量缓存(Incremental Cache):存储缓存数据
    2. 队列(Queue):同步和去重基于时间的重新验证
    3. 标签缓存(Tag Cache):用于通过revalidateTagrevalidatePath进行按需重新验证

    缓存实现

    而对于这三者的实现我采用的是:

    • 增量缓存:使用R2存储数据,源码地址
    • 队列:使用由Durable Objects实现的时间验证队列,源码地址
    • 标签缓存:使用由Durable Objects实现的缓存验证系统,源码地址

    R2增量缓存实现原理

    R2增量缓存是Next.js在Cloudflare环境中存储缓存数据的主要机制。它利用Cloudflare的R2对象存储服务,提供高性能、低成本的数据存储解决方案。

    R2增量缓存实现了标准的Next.js增量缓存接口,包括getsetdelete方法,使其能够无缝集成到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
        })
      }
    }
    
    会员专享

    订阅后解锁完整文章

    支持创作、解锁全文,未来更新也会第一时间送达。

    评论

    加入讨论

    0 条评论
    登录后评论

    还没有评论,来占个沙发吧。