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 快速设置
设置 Cloudflare Worker 的最快方式:
npx jamdesk deploy-proxy cloudflare
此交互式命令将:
- 检查是否已安装 wrangler 3.0+
- 验证你的 Cloudflare 账户并显示可用域名
- 从
docs.json中关联的项目解析你的 Jamdesk 子域名 - 让你从 Cloudflare 区域中选择目标域名
- 生成所有必需文件
- 可选部署到 Cloudflare
如果你可以访问多个 Cloudflare 账户(代理机构或团队常见此情况),CLI 会在选择区域之前提示你选择账户。选择拥有你要部署域名的账户——Worker 路由只能为所选账户中的域名创建。
非交互式设置
要在无需提示的情况下运行——例如在 CI 中或从脚本运行——请通过标志传入答案:
jamdesk deploy-proxy cloudflare --slug myproject --domain example.com --yes
--yes 会生成 Worker 文件,然后停止。它不会执行部署:由于区域是根据你的域名推断的,而不是通过 Cloudflare 账户确认的,因此将该配置推送到线上仍是一个明确的步骤。使用以下命令完成部署:
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 创建新目录并初始化:
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 上提供流量,请添加第二条路由,以便 Worker 同时处理两个域名:
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>;如果你已经自定义了 Worker,请手动编辑数组。
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 未运行。确保你的 DNS 记录在 Cloudflare 中设置为代理(橙色云朵)。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