Cloudflare
操作指南
多 Worker 高级方案
⚠️

这是一个高级功能,需要很好地理解 OpenNext 和 Cloudflare Workers。 此高级设置不能与以下功能一起使用:

  • 预览 URL(暂存部署)
  • 版本倾斜保护功能
  • 标准的 @opennextjs/cloudflare deploy 命令

在继续之前请仔细考虑这些限制。

OpenNext 允许您将应用程序拆分为多个 worker 中更小、更轻的部分。这可以提高性能并减少应用程序的内存占用。 这是一个更高级的功能,不支持通过标准的 @opennextjs/cloudflare deploy 命令进行部署。

例如,我们将中间件拆分到其自己的 worker 中,并将应用程序的其余部分拆分到另一个 worker 中。您可以通过为特定路由或功能创建额外的 worker 来进一步拆分应用程序,但此处不再赘述。 当此处提到中间件时,我们指的是您构建的中间件以及 OpenNext 的路由层。

您可以在 GitBook 仓库 (opens in a new tab) 中找到此类部署的示例。

何时使用此设置

当您需要以下情况时,这种多 worker 方法是有益的:

  • 减少单个 worker 的内存占用
  • 通过将轻量级中间件拆分到其自己的 worker 并从那里提供 ISR/SSG 请求,提高冷启动性能

open-next.config.ts

这里我们假设配置如下:

import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
import { withRegionalCache } from "@opennextjs/cloudflare/overrides/incremental-cache/regional-cache";
import doShardedTagCache from "@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache";
import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue";
import { purgeCache } from "@opennextjs/cloudflare/overrides/cache-purge/index";
 
export default defineCloudflareConfig({
  incrementalCache: withRegionalCache(r2IncrementalCache, { mode: "long-lived" }),
  queue: doQueue,
  // 仅当您使用按需重新验证时才需要此项
  tagCache: doShardedTagCache({
    baseShardSize: 12,
    regionalCache: true, // 启用区域缓存以减少 DO 的负载并提高速度
    regionalCacheTtlSec: 3600, // 标签缓存区域缓存的 TTL
    regionalCacheDangerouslyPersistMissingTags: true, // 启用此项以在区域缓存中持久化缺失的标签
    shardReplication: {
      numberOfSoftReplicas: 4,
      numberOfHardReplicas: 2,
      regionalReplication: {
        defaultRegion: "enam",
      },
    },
  }),
  enableCacheInterception: true,
  // 您也可以使用 durableObject 选项将持久化对象用作缓存清除
  cachePurge: purgeCache({ type: "direct" }),
});

自定义 worker

为了使此功能正常工作,您需要 2 个自定义 worker:

// middleware.js
import { WorkerEntrypoint } from "cloudflare:workers";
 
// ./.open-next/cloudflare/init.js
import { runWithCloudflareRequestContext } from "./.open-next/cloudflare/init.js";
 
import { handler as middlewareHandler } from "./.open-next/middleware/handler.mjs";
 
export { DOQueueHandler } from "./.open-next/.build/durable-objects/queue.js";
 
export { DOShardedTagCache } from "./.open-next/.build/durable-objects/sharded-tag-cache.js";
 
export default class extends WorkerEntrypoint {
  async fetch(request) {
    return runWithCloudflareRequestContext(request, this.env, this.ctx, async () => {
      // 通过 Next.js 中间件层和 OpenNext 路由层处理请求
      const reqOrResp = await middlewareHandler(request, this.env, this.ctx);
 
      // 如果中间件返回 Response,则直接发送(例如,重定向、拦截、ISR/SSG 缓存命中)
      if (reqOrResp instanceof Response) {
        return reqOrResp;
      }
 
      // 将修改后的请求转发到服务器 worker
      // 版本亲和性确保一致的 worker 版本
      // https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-affinity
      reqOrResp.headers.set("Cloudflare-Workers-Version-Overrides", `server="${this.env.WORKER_VERSION_ID}"`);
 
      // 代理到服务器 worker,禁用动态内容的缓存
      return this.env.DEFAULT_WORKER.fetch(reqOrResp, {
        // 我们原样返回重定向
        redirect: "manual",
        cf: {
          cacheEverything: false,
        },
      });
    });
  }
}
// server.js
 
// 替换为您实际的构建输出目录,通常为:
// ./.open-next/cloudflare/init.js
import { runWithCloudflareRequestContext } from "./.open-next/cloudflare/init.js";
 
import { handler } from "./.open-next/server-functions/default/handler.mjs";
 
export default {
  async fetch(request, env, ctx) {
    return runWithCloudflareRequestContext(request, env, ctx, async () => {
      // - Request 由 Next 服务器处理
      return handler(request, env, ctx);
    });
  },
};

Wrangler 配置

// 中间件 wrangler 文件
{
  "main": "middleware.js",
  "name": "middleware",
  "compatibility_date": "2025-04-14",
  "compatibility_flags": ["nodejs_compat", "allow_importable_env", "global_fetch_strictly_public"],
  // 中间件提供资产服务
  "assets": {
    "directory": "../../.open-next/assets",
    "binding": "ASSETS",
  },
  "vars": {
    // 此项需要在每次部署时替换
    "WORKER_VERSION_ID": "TO_REPLACE",
  },
  "routes": [
    // 在此处定义您的路由,而不是在 server.js 中
  ],
  "r2_buckets": [
    {
      "binding": "NEXT_INC_CACHE_R2_BUCKET",
      "bucket_name": "<BUCKET_NAME>",
    },
  ],
  "services": [
    {
      "binding": "WORKER_SELF_REFERENCE",
      "service": "middleware",
    },
    {
      "binding": "DEFAULT_WORKER",
      "service": "main-server",
    },
  ],
  "durable_objects": {
    "bindings": [
      {
        "name": "NEXT_TAG_CACHE_DO_SHARDED",
        "class_name": "DOShardedTagCache",
      },
      {
        "name": "NEXT_CACHE_DO_QUEUE",
        "class_name": "DOQueueHandler",
      },
    ],
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"],
    },
  ],
}
// 服务器 wrangler 文件
{
  "main": "server.js",
  "name": "main-server",
  "compatibility_date": "2025-04-14",
  "compatibility_flags": ["nodejs_compat", "allow_importable_env", "global_fetch_strictly_public"],
  "r2_buckets": [
    {
      "binding": "NEXT_INC_CACHE_R2_BUCKET",
      "bucket_name": "<BUCKET_NAME>",
    },
  ],
  "services": [
    {
      "binding": "WORKER_SELF_REFERENCE",
      // 注意:此处必须引用中间件 worker,*而不是* 服务器。
      "service": "middleware",
    },
  ],
  "durable_objects": {
    "bindings": [
      {
        "name": "NEXT_TAG_CACHE_DO_SHARDED",
        "class_name": "DOShardedTagCache",
        "script_name": "middleware",
      },
      {
        "name": "NEXT_CACHE_DO_QUEUE",
        "class_name": "DOQueueHandler",
        "script_name": "middleware",
      },
    ],
  },
}

实际部署

您不能使用 @opennextjs/cloudflare deploy 来部署此设置,因为它不适用于多 worker 设置。

  1. 服务器上传 → 获取版本 ID
  2. 中间件准备 → 更新版本引用
  3. 中间件上传 → 获取版本 ID
  4. 逐步推出 → 服务器 (0%) → 中间件 (100%) → 服务器 (100%)

为了使此功能正常工作,您需要使用 wrangler CLI 分别部署每个 worker,并为每次部署覆盖中间件 wrangler 配置中的 WORKER_VERSION_ID 变量。 请注意,我们使用逐步部署作为在不影响当前运行版本的情况下部署新版本的解决方案。

在不导致已部署版本停机的情况下进行部署的步骤如下:

  1. 首先您需要上传服务器 worker 的新版本 wrangler versions upload --config ./path-to/serverWrangler.jsonc
  2. 然后您需要从上一步命令的输出中提取服务器的新版本 id。控制台输出中显示的值是 Worker Version ID: <ID>。此值在第 8 步中称为 NEW_SERVER_VERSION_ID
  3. 在上传中间件之前,您需要将中间件 wrangler 配置中的 WORKER_VERSION_ID 变量替换为上一步中的新服务器版本 id。
  4. 然后您需要上传中间件 worker 的新版本 wrangler versions upload --config ./path-to/middlewareWrangler.jsonc。检索版本 id,您将在第 9 步中需要它(NEW_MIDDLEWARE_ID)。
  5. 并从上一条命令的输出中提取中间件的新版本 id。控制台输出中显示的值是 Worker Version ID: <ID>
  6. 使用 wrangler deployments status --config ./path-to/server-wrangler.jsonc 获取当前部署的服务器版本 id
  7. 从上一步命令的输出中提取服务器的版本 id。此值在第 8 步中称为 CURRENT_SERVER_ID
  8. 然后您使用逐步部署将第 1 步上传的服务器部署到 0% wrangler versions deploy <CURRENT_SERVER_ID>@100% <NEW_SERVER_VERSION_ID>@0% -y --config ./path-to/server-wrangler.jsonc
  9. 然后您部署 100% 的中间件 wrangler versions deploy <NEW_MIDDLEWARE_ID>@100% -y --config ./path-to/middlewareWrangler.jsonc。在此阶段,您已经在生产中提供网站的新版本。
  10. 最后,您部署 100% 的服务器 wrangler versions deploy <NEW_SERVER_VERSION_ID>@100% -y --config ./path-to/server-wrangler.jsonc

您可以在 这里 (opens in a new tab) 找到 GitBook 仓库中使用 GitHub Actions 的此类部署的实际实现。

版本亲和性解释

版本亲和性确保请求被路由到运行兼容版本的 worker:

  • 中间件设置 Cloudflare-Workers-Version-Overrides
  • 这强制请求进入正确的服务器 worker 版本。
  • 防止部署期间的版本不匹配