Jamdesk Documentation logo

Cloudflare Workers

通过 Cloudflare Worker 将 /docs 请求代理到你的 Jamdesk 文档站点。涵盖 Worker 设置、路由模式和缓存配置。

Cloudflare Worker 会拦截你域名上 /docs 的请求,将其重写到你的 Jamdesk 子域名,并返回响应。它在边缘运行,无需源服务器。可使用 npx jamdesk deploy-proxy cloudflare 生成 Worker,或按下文手动设置。

工作原理

Worker 会将请求转发到你的 Jamdesk 子域名,并通过 X-Jamdesk-Forwarded-Host 请求头传递你的域名。Jamdesk 读取该请求头来验证域名并应用相应设置。这是一次性设置。之后在仪表板中更改域名或配置时,无需更新 Worker。

前置条件

  • 已配置好域名的 Cloudflare 账户
  • 已安装 Wrangler CLI v3.0+
  • 你的 Jamdesk 子域名(可在仪表板设置中找到)
  • 已在 Jamdesk 仪表板中为项目添加自定义域名(在域名注册并验证之前,Worker 会返回 403)
  • 为提供文档服务的主机名配置一条已代理(橙色云朵)的 DNS 记录。Worker 仅在已代理的记录上运行。即使域名不承载其他内容,也需要一条记录:添加指向 100:: 的占位 AAAA 记录,并将其设为已代理。

使用 CLI 快速设置

使用一条命令设置 Worker:

npx jamdesk deploy-proxy cloudflare

该交互式命令会:

  1. 检查是否已安装 wrangler 3.0+
  2. 验证你的 Cloudflare 账户并显示可用域名
  3. 从 docs.json 中关联的项目解析你的 Jamdesk 子域名
  4. 让你从 Cloudflare 区域中选择目标域名
  5. 生成所有必需文件
  6. 可选择部署到 Cloudflare

如果你有多个 Cloudflare 账户(代理机构或团队中很常见),CLI 会在选择区域前提示你选择一个账户。请选择拥有目标域名的账户。Workers 路由只能为所选账户中的域名创建。

非交互式设置

将答案作为参数传入,即可在 CI 或脚本中无提示运行:

jamdesk deploy-proxy cloudflare --slug myproject --domain example.com --yes

--yes 只会生成 Worker 文件,并不会部署。区域是根据你的域名推断的,并未与你的 Cloudflare 账户核对。将配置推送上线仍需手动执行。最后运行:

cd cloudflare-worker
npx wrangler deploy

--yes 无法提示输入子域名。CLI 会从 docs.json 中关联的项目读取。如果缺少该关联,请传入 --slug(或先运行一次 jamdesk deploy 添加关联)。否则命令会停止,而不是猜测。

如果输出目录已存在,--yes 会停止而不是替换它。添加 --force 可覆盖,或使用 --output-dir 写入其他位置。

选项说明
--slug你的 Jamdesk 子域名,即 X.jamdesk.app 中的 X(跳过自动检测)
--domain目标域名(例如 yoursite.com)
--path路径前缀;必须与仪表板中的子路径完全一致(默认:/docs)
--output-dir输出目录(默认:cloudflare-worker/)
--skip-deploy在交互式运行中跳过"是否立即部署?"的提示(--yes 从不部署)
--force若输出目录已存在则覆盖
--yes对所有提示采用默认答案(CI 模式)。从不部署。除非同时使用 --force,否则从不覆盖已存在的目录

或者按照下面的手动步骤操作。


手动设置

步骤 1:创建 Worker

为 Worker 创建一个新目录并初始化:

mkdir docs-proxy && cd docs-proxy
npm init -y

步骤 2:添加 Worker 代码

创建 index.js,内容如下:

index.js
/**
 * Jamdesk Documentation Proxy Worker
 *
 * Generated by: jamdesk deploy-proxy cloudflare
 * Proxies /docs/* requests AND their assets to YOUR_SLUG.jamdesk.app
 *
 * Assets under /_jd/* (images, fonts, branding, analytics) must also be proxied
 * since they use absolute paths in the HTML.
 */

const JAMDESK_HOST = "YOUR_SLUG.jamdesk.app";

// Paths that are always proxied to Jamdesk
const PROXY_PATHS = [
  "/docs",  // Documentation pages
  "/_jd/",  // All Jamdesk assets (images, fonts, branding, analytics)
];

function shouldProxy(pathname) {
  return PROXY_PATHS.some(prefix => {
    // For the docs path, require exact match or prefix with slash (not /docs.json)
    if (prefix === "/docs") {
      return pathname === "/docs" || pathname.startsWith("/docs/");
    }
    return pathname.startsWith(prefix);
  });
}

function proxyToJamdesk(request, url) {
  // Rewrite the request to Jamdesk
  const proxyUrl = new URL(request.url);
  proxyUrl.hostname = JAMDESK_HOST;
  // Pin the scheme: an http:// visitor proxied as http:// gets a 308 from
  // Vercel's edge pointing at JAMDESK_HOST, and redirect:"manual" hands that
  // redirect straight to the browser — bouncing the visitor off this domain.
  proxyUrl.protocol = "https:";
  // Setting .protocol leaves a non-default .port in place, so :8787 (wrangler
  // dev) or Cloudflare's alternate http ports (8080, 8880, 2052…) would follow
  // us to the https upstream and fail there.
  proxyUrl.port = "";

  // Clone headers and add proxy headers
  const headers = new Headers(request.headers);
  headers.set("Host", JAMDESK_HOST);
  headers.set("X-Forwarded-Host", url.hostname);
  headers.set("X-Forwarded-Proto", "https");
  // Custom header for domain verification (Vercel strips standard forwarding headers)
  headers.set("X-Jamdesk-Forwarded-Host", url.hostname);

  // Don't follow redirects — let the browser handle them so the URL updates.
  // Without this, redirects happen internally and the browser URL doesn't change,
  // which causes the sidebar to mis-highlight the active page.
  const proxyRequest = new Request(proxyUrl, {
    method: request.method,
    headers,
    body: request.body,
    redirect: "manual",
  });

  // Cache all content types at Cloudflare edge (CF doesn't cache HTML by default).
  // Cache duration is controlled by upstream Cache-Control headers.
  // Never cache redirects or errors — they must always hit origin.
  return fetch(proxyRequest, {
    cf: {
      cacheEverything: true,
      cacheTtlByStatus: { "300-399": 0, "400-499": 0, "500-599": 0 },
    },
  });
}

export default {
  async fetch(request) {
    const url = new URL(request.url);

    if (shouldProxy(url.pathname)) {
      return proxyToJamdesk(request, url);
    }

    // /_next/ is ambiguous: the customer's root site may itself be a Next.js app
    // serving its own /_next/ assets. Try the origin first and fall back to
    // Jamdesk when the origin doesn't have it (404) or can't answer at all
    // (5xx — a docs-only domain has no real origin, so Cloudflare returns 522).
    // Hashed asset filenames never collide between the two apps.
    if (url.pathname.startsWith("/_next/")) {
      let originResponse;
      try {
        originResponse = await fetch(request);
      } catch (err) {
        // Log rather than swallow: a genuine platform fault and a domain with
        // no origin at all are indistinguishable in `wrangler tail` otherwise.
        console.warn("origin fetch threw, serving from Jamdesk:", err);
        return proxyToJamdesk(request, url);
      }
      if (originResponse.status !== 404 && originResponse.status < 500) {
        return originResponse;
      }
      return proxyToJamdesk(request, url);
    }

    return fetch(request);
  },
};

将 YOUR_SLUG 替换为你的 Jamdesk 子域名(例如,若文档位于 acme.jamdesk.app,则填 acme)。

如果你使用的是仪表板中自定义的子路径而非默认的 /docs,请在 PROXY_PATHS 以及 shouldProxy() 内的精确匹配检查中,将 "/docs" 替换为你的子路径。CLI 的 --path 参数在生成文件时会执行此操作,但仅针对未修改的模板。重新生成会覆盖你手动添加到 PROXY_PATHS 中的自定义条目。如果你已自定义此 Worker,请直接就地编辑 /docs 条目。

X-Jamdesk-Forwarded-Host 请求头是必需的。缺少该请求头会静默失败:请求仍会成功,但页面会以 noindex 提供服务,且规范链接指向 YOUR_SLUG.jamdesk.app 而非你的域名。搜索引擎将不会索引你的文档。403 则是相反的问题:请求头存在,但它指向的域名并未针对此项目注册并激活。

步骤 3:配置 wrangler.toml

创建 wrangler.toml 以配置你的 Worker:

wrangler.toml
name = "docs-proxy"
main = "index.js"
compatibility_date = "2024-01-01"

# Serve only via the routes below, not on the public <name>.workers.dev URL —
# that URL is a second way into the same proxy and is worth closing.
workers_dev = false

# Single catch-all route; the worker handles path filtering internally
routes = [
  { pattern = "yoursite.com/*", zone_name = "yoursite.com" },
]

如果你的 Cloudflare 登录可访问多个账户,还需添加 account_id = "<your account id>"。否则 wrangler deploy 会停止,而不是猜测要部署到哪个账户。npx wrangler whoami 会列出你的账户 ID。

如果你的站点还在 www.yoursite.com 上提供流量,可添加第二条路由:

routes = [
  { pattern = "yoursite.com/*", zone_name = "yoursite.com" },
  { pattern = "www.yoursite.com/*", zone_name = "yoursite.com" },
]

步骤 4:部署

将 Worker 部署到 Cloudflare:

npx wrangler deploy

步骤 5:验证

访问 https://yoursite.com/docs,确认文档可以正常加载。

故障排除

如果你在仪表板中重命名了子路径(例如 /docs → /help),但 Worker 的 PROXY_PATHS 中仍只列出 /docs,那么 /help/* 的请求将永远不会到达 Jamdesk。它们会落入 fetch(request),并在你自己的源站返回 404。与此同时 /docs/* 仍可正常工作(Jamdesk 同时提供两个前缀),这使得问题很容易被忽略。

**解决方法:**将新子路径添加到 PROXY_PATHS。如果 Worker 仍是未修改的模板,可重新运行 jamdesk deploy-proxy cloudflare --path <subpath>。如果你已自定义该数组,请手动编辑。

Wrangler 优先使用 CLOUDFLARE_API_TOKEN 而非其 OAuth 登录。在该令牌被设置时,它无法启动 OAuth 登录。此命令需要 OAuth 来列出你账户中的区域。它会停止并显示令牌的所在位置,而不是在 wrangler 内部失败。

Wrangler 还会读取你运行所在目录中的 .env。令牌可以仅为 wrangler 设置,而不在你的 shell 中。echo $CLOUDFLARE_API_TOKEN 为空并不能排除这种情况。也请检查当前目录中是否有 .env。

**解决方法:**为令牌授予 account:read 和 zone:read 权限,然后重新运行。或者不使用该令牌运行:

env -u CLOUDFLARE_API_TOKEN jamdesk deploy-proxy cloudflare

当令牌来自 .env 时,env -u 无效。请在不含该 .env 的目录中运行命令,或在本次运行期间将该文件移开。

CLI 会在选择区域之前显示你可用的域名。如果看到"No domains found":

  1. 确认你已登录到正确的 Cloudflare 账户
  2. 检查你的域名是否已在 Cloudflare 仪表板中添加并处于激活状态
  3. 再次运行 CLI,并在询问是否继续使用当前账户时选择"No",以切换账户

如果你有多个 Cloudflare 账户:

  1. 运行 jamdesk deploy-proxy cloudflare
  2. 在提示选择账户时,选择包含你域名的那个
  3. 若要使用完全不同的登录,选择 "Switch to different login"
  4. CLI 会让你退出登录,并提示你使用正确的凭据登录

此错误表示所选区域与你的 Cloudflare 账户不匹配。可能是:

  • 你选择的区域属于其他账户
  • 该区域已从 Cloudflare 中移除

解决方法:重新运行 CLI 并从列表中选择正确的区域,或切换到拥有该区域的账户。

请使用通配路由模式:yoursite.com/*(而不是仅 yoursite.com/docs*)。Worker 内部的 shouldProxy() 函数会负责过滤路径。

两个常见原因:

  1. Worker 未运行:在 Cloudflare 中将 DNS 记录设为已代理(橙色云朵)。Worker 仅在已代理的记录上运行。
  2. **缺少 X-Forwarded-Host 请求头:**Worker 必须设置此请求头。Jamdesk 需要它来生成正确的资源 URL。

如果你看到"Domain is not authorized to serve this content":

  1. 确认你的域名已在 Jamdesk 仪表板中注册
  2. 完成域名的 DNS 验证(TXT 记录)
  3. 在 Worker 代码中设置 X-Jamdesk-Forwarded-Host 请求头
  4. 检查你的域名是否映射到正确的项目

域名必须通过验证,Worker 才能提供文档服务。

Worker 仅在已代理(橙色云朵)的 DNS 记录上运行。如果你的 A 记录设为"仅 DNS"(灰色云朵),请求会直接发送到源站,跳过 Worker。

**解决方法:**在 Cloudflare DNS 中将 A 记录切换为已代理(橙色云朵)。任何带有 Worker 路由的记录都必须已代理,子域名也不例外。

Jamdesk 通过直接读取你的 DNS 记录值来验证所有权。Cloudflare 的代理(橙色云朵)会隐藏这些值。在代理开启时,验证无法完成。

解决方法:

  1. 将 DNS 记录设为仅 DNS(灰色云朵)
  2. 等待验证完成(仪表板中状态变为 active)
  3. 再切换回已代理(橙色云朵),以便 Worker 运行

Jamdesk 以 Cache-Control: no-store 提供文档 HTML。Cloudflare 不会在边缘缓存页面(cf-cache-status: BYPASS)。每次请求都会渲染当前版本,已发布的更改无需等待缓存即可生效。

/_next/ 和 /_jd/ 下的静态资源(JavaScript、CSS、字体、图片)带有长期有效的 immutable 缓存头。Cloudflare 会在边缘缓存它们。它们的文件名带有内容哈希,每次构建都会生成新的 URL,因此无需清除缓存。

cacheEverything: true 允许 Cloudflare 在已代理路由上缓存这些静态资源。它不会覆盖 HTML 上的 no-store。如需手动清除边缘缓存,请使用 Cloudflare 的 Purge Cache(Caching → Configuration → Purge Everything)。

CLI 要求 wrangler 3.0+。使用以下命令更新:

npm install -g wrangler@latest

下一步

仅自定义域名

阻止你的子域名直接响应

自定义域名

验证 DNS 并排查故障

子路径托管

在 /docs 提供文档服务