---
title: Cloudflare Workers 代理
description: "通过 Cloudflare Worker 将 /docs 请求代理到 Jamdesk 文档站点，涵盖 Worker 设置、路由模式和缓存配置。"
---

> **For AI agents:** the complete documentation index is at [llms.txt](/docs/llms.txt). Append `.md` to any page URL for its markdown version.

[Cloudflare Worker](https://workers.cloudflare.com/) 会在你的域名上拦截 `/docs` 请求，将其重写到你的 Jamdesk 子域名并返回响应。整个过程都在边缘完成，无需源站服务器。你可以使用 `npx jamdesk deploy-proxy cloudflare` 自动创建 Worker，也可以按照下面的步骤手动设置。

## 工作原理

Worker 会将请求转发到你的 Jamdesk 子域名，并在 `X-Jamdesk-Forwarded-Host` 标头中传递你的域名。Jamdesk 使用此标头验证域名并应用相应设置。这是一次性设置——以后如果你在仪表板中更改域名或配置，无需更新 Worker。

## 前置条件

- 已配置域名的 Cloudflare 账户
- 已安装 [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) v3.0+
- 你的 Jamdesk 子域名（可在仪表板设置中找到）
- 已将自定义域名添加到 Jamdesk 仪表板中的项目（在域名注册并验证之前，Worker 会返回 403）
- 为提供文档的主机名配置了**代理**（橙色云朵）DNS 记录。Worker 只能在已代理的记录上运行，因此即使该域名没有托管其他内容，也必须配置一条记录——添加一条指向 `100::` 的占位 `AAAA` 记录，并将其设为代理状态。

## 使用 CLI 快速设置

设置 Cloudflare Worker 的最快方式：

```bash
npx jamdesk deploy-proxy cloudflare
```

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

如果你可以访问多个 Cloudflare 账户（代理机构或团队常见此情况），CLI 会在选择区域之前提示你选择账户。选择拥有你要部署域名的账户——Worker 路由只能为所选账户中的域名创建。

### 非交互式设置

要在无需提示的情况下运行——例如在 CI 中或从脚本运行——请通过标志传入答案：

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

`--yes` 会生成 Worker 文件，然后停止。它不会执行部署：由于区域是根据你的域名推断的，而不是通过 Cloudflare 账户确认的，因此将该配置推送到线上仍是一个明确的步骤。使用以下命令完成部署：

```bash
cd cloudflare-worker
npx wrangler deploy
```

使用 `--yes` 时 CLI 无法进行提示，因此需要知道你的子域名。它会从 `docs.json` 中关联的项目读取子域名；如果缺少该关联（运行一次 `jamdesk deploy` 即可添加），请显式传入 `--slug`，否则命令会停止而不是进行猜测。

如果输出目录已存在，`--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 创建新目录并初始化：

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

### 步骤 2：添加 Worker 代码

创建包含以下代码的 `index.js` 文件：

```javascript 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);
  },
};
```

<Note>
将 `YOUR_SLUG` 替换为你的实际 Jamdesk 子域名（例如，如果你的文档地址为 `acme.jamdesk.app`，则替换为 `acme`）。
</Note>

<Note>
如果你在仪表板中使用的是自定义子路径，而不是默认的 `/docs`，请在 `PROXY_PATHS` 中以及 `shouldProxy()` 内部的精确匹配检查中，将 `"/docs"` 替换为你的子路径。CLI 的 `--path` 标志会在生成文件时自动完成此操作，但只适用于**未修改**的模板；重新生成文件会覆盖你手动添加到 `PROXY_PATHS` 的自定义条目。如果你已自定义此 Worker，请直接编辑其中的 `/docs` 条目。
</Note>

<Warning>
`X-Jamdesk-Forwarded-Host` 标头是**必需的**，而缺少该标头会静默失败——请求仍会成功，但页面会以 `noindex` 提供，规范链接会指向 `YOUR_SLUG.jamdesk.app` 而不是你的域名，因此搜索引擎永远不会将你的文档编入索引。**403** 则是相反的问题：标头确实存在，但其中指定的域名尚未为此项目注册并激活。
</Warning>

### 步骤 3：配置 wrangler.toml

创建 `wrangler.toml` 以配置 Worker：

```toml 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" },
]
```

<Note>
如果你的 Cloudflare 登录账户可以访问多个账户，请同时添加 `account_id = "<your account id>"`——否则 `wrangler deploy` 会停止，而不是猜测要部署到哪个账户。`npx wrangler whoami` 会列出你的账户 ID。
</Note>

<Tip>
如果你的网站也在 `www.yoursite.com` 上提供流量，请添加第二条路由，以便 Worker 同时处理两个域名：

```toml
routes = [
  { pattern = "yoursite.com/*", zone_name = "yoursite.com" },
  { pattern = "www.yoursite.com/*", zone_name = "yoursite.com" },
]
```
</Tip>

### 步骤 4：部署

将 Worker 部署到 Cloudflare：

```bash
npx wrangler deploy
```

### 步骤 5：验证

访问 `https://yoursite.com/docs`，确认文档能够正常提供服务。

## 故障排除

<Accordion title="在仪表板中重命名后新子路径返回 404">
如果你在仪表板中重命名了子路径（例如从 `/docs` → `/help`），但 Worker 的 `PROXY_PATHS` 仍只列出 `/docs`，则对 `/help/*` 的请求永远不会到达 Jamdesk：它们会继续执行 `fetch(request)`，并在你自己的源站返回 404。与此同时，`/docs/*` 仍然可以正常工作（Jamdesk 会同时提供两个前缀），这正是该问题容易被忽略的原因。

**修复方法：**将新子路径添加到 `PROXY_PATHS`。如果 Worker 仍是未修改的模板，请重新运行 `jamdesk deploy-proxy cloudflare --path <subpath>`；如果你已经自定义了 Worker，请手动编辑数组。
</Accordion>

<Accordion title="“You are logged in with an API Token. Unset the CLOUDFLARE_API_TOKEN…”">
Wrangler 优先使用 `CLOUDFLARE_API_TOKEN`，而不是 OAuth 登录；设置该令牌后，它无法启动 OAuth 登录。此命令需要 OAuth 来列出你账户中的区域，因此它会显示令牌的位置并停止，而不是在 wrangler 内部失败。

Wrangler 还会从运行命令的目录读取 `.env`，因此令牌可能已为 wrangler 设置，却不在你的 shell 环境中——即使 `echo $CLOUDFLARE_API_TOKEN` 没有输出，也不能排除这种情况。请同时检查当前目录中的 `.env` 文件。

**修复方法：**为令牌授予 `account:read` 和 `zone:read` 权限后重新运行，或者在不使用令牌的情况下运行：

```bash
env -u CLOUDFLARE_API_TOKEN jamdesk deploy-proxy cloudflare
```

如果令牌来自 `.env`，`env -u` 不会生效——请从没有此类 `.env` 的目录运行命令，或在本次运行期间暂时移走该文件。
</Accordion>

<Accordion title="此账户中未找到域名">
CLI 会在选择区域之前显示可用域名。如果看到“No domains found”：
1. 确认你已登录正确的 Cloudflare 账户
2. 检查你的域名是否已添加到 Cloudflare 仪表板并处于激活状态
3. 再次运行 CLI，并在询问是否继续使用当前账户时选择“No”，以切换账户
</Accordion>

<Accordion title="Cloudflare 账户错误">
如果你有多个 Cloudflare 账户：
1. 运行 `jamdesk deploy-proxy cloudflare`
2. 出现账户选择提示时，选择拥有你域名的账户
3. 如果需要完全不同的登录信息，请选择 **"Switch to different login"**
4. CLI 会将你登出，并提示你使用正确的凭据登录
</Accordion>

<Accordion title="部署期间找不到区域">
此错误表示所选区域与 Cloudflare 账户不匹配。原因可能是：
- 你选择的区域属于其他账户
- 该区域已从 Cloudflare 中移除

修复方法：重新运行 CLI 并从列表中选择正确的区域，或切换到拥有该区域的账户。
</Accordion>

<Accordion title="文档页面出现 404 错误">
确保你的路由模式使用全匹配：`yoursite.com/*`（不要只使用 `yoursite.com/docs*`）。Worker 内部的 `shouldProxy()` 函数会处理路径筛选。
</Accordion>

<Accordion title="资源未正确加载">
两个常见原因：

1. **Worker 未运行。**确保你的 DNS 记录在 Cloudflare 中设置为**代理**（橙色云朵）。Worker 只能在已代理的记录上运行。
2. **缺少 `X-Forwarded-Host` 标头。**Worker 必须设置此标头，以便 Jamdesk 生成正确的资源 URL。
</Accordion>

<Accordion title="403 域名未授权错误">
如果看到“Domain is not authorized to serve this content”：

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

必须先验证域名，Worker 才能提供文档。
</Accordion>

<Accordion title="Worker 未在根域名上触发">
Worker 只能在**代理**（橙色云朵）DNS 记录上运行。如果你的 A 记录设置为“仅 DNS”（灰色云朵），请求会直接发送到源站，完全跳过 Worker。

**修复方法：**在 Cloudflare DNS 中，将 A 记录切换为代理状态（橙色云朵）。子域名同样适用：任何配置了 Worker 路由的记录都必须启用代理。
</Accordion>

<Accordion title="域名验证一直处于 Pending 状态">
Jamdesk 会直接读取 DNS 记录值来验证所有权。Cloudflare 的代理（橙色云朵）会隐藏这些值，因此验证无法完成。

**修复方法：**
1. 将 DNS 记录设置为“仅 DNS”（灰色云朵）
2. 等待验证完成（仪表板中的状态变为 **active**）
3. 切换回**代理**（橙色云朵），使 Worker 运行

简而言之：验证时使用**灰色云朵** → 提供服务时使用**橙色云朵**。
</Accordion>

<Accordion title="缓存的工作原理">
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）。
</Accordion>

<Accordion title="Wrangler 版本过旧">
CLI 要求 wrangler 3.0+。使用以下命令更新：

```bash
npm install -g wrangler@latest
```
</Accordion>

## 下一步

<Columns cols={3}>
  <Card title="仅自定义域名" icon="eye-slash" href="/cn/deploy/custom-domain-only">
    停止直接响应你的子域名
  </Card>
  <Card title="自定义域名" icon="globe" href="/cn/deploy/custom-domains">
    验证 DNS 并排查问题
  </Card>
  <Card title="子路径托管" icon="folder-tree" href="/cn/deploy/subpath-hosting">
    在 /docs 提供文档
  </Card>
</Columns>