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
该交互式命令会:
- 检查是否已安装 wrangler 3.0+
- 验证你的 Cloudflare 账户并显示可用域名
- 从
docs.json中关联的项目解析你的 Jamdesk 子域名 - 让你从 Cloudflare 区域中选择目标域名
- 生成所有必需文件
- 可选择部署到 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,内容如下:
/**
* 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:
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":
- 确认你已登录到正确的 Cloudflare 账户
- 检查你的域名是否已在 Cloudflare 仪表板中添加并处于激活状态
- 再次运行 CLI,并在询问是否继续使用当前账户时选择"No",以切换账户
如果你有多个 Cloudflare 账户:
- 运行
jamdesk deploy-proxy cloudflare - 在提示选择账户时,选择包含你域名的那个
- 若要使用完全不同的登录,选择 "Switch to different login"
- CLI 会让你退出登录,并提示你使用正确的凭据登录
此错误表示所选区域与你的 Cloudflare 账户不匹配。可能是:
- 你选择的区域属于其他账户
- 该区域已从 Cloudflare 中移除
解决方法:重新运行 CLI 并从列表中选择正确的区域,或切换到拥有该区域的账户。
请使用通配路由模式:yoursite.com/*(而不是仅 yoursite.com/docs*)。Worker 内部的 shouldProxy() 函数会负责过滤路径。
两个常见原因:
- Worker 未运行:在 Cloudflare 中将 DNS 记录设为已代理(橙色云朵)。Worker 仅在已代理的记录上运行。
- **缺少
X-Forwarded-Host请求头:**Worker 必须设置此请求头。Jamdesk 需要它来生成正确的资源 URL。
如果你看到"Domain is not authorized to serve this content":
- 确认你的域名已在 Jamdesk 仪表板中注册
- 完成域名的 DNS 验证(TXT 记录)
- 在 Worker 代码中设置
X-Jamdesk-Forwarded-Host请求头 - 检查你的域名是否映射到正确的项目
域名必须通过验证,Worker 才能提供文档服务。
Worker 仅在已代理(橙色云朵)的 DNS 记录上运行。如果你的 A 记录设为"仅 DNS"(灰色云朵),请求会直接发送到源站,跳过 Worker。
**解决方法:**在 Cloudflare DNS 中将 A 记录切换为已代理(橙色云朵)。任何带有 Worker 路由的记录都必须已代理,子域名也不例外。
Jamdesk 通过直接读取你的 DNS 记录值来验证所有权。Cloudflare 的代理(橙色云朵)会隐藏这些值。在代理开启时,验证无法完成。
解决方法:
- 将 DNS 记录设为仅 DNS(灰色云朵)
- 等待验证完成(仪表板中状态变为 active)
- 再切换回已代理(橙色云朵),以便 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