Jamdesk Documentation logo

Vercel

了解在 Vercel 部署的域名 /docs 路径下提供 Jamdesk 文档的两种方式:vercel.json 重写或 Edge Middleware。

在你 Vercel 部署的域名上的 /docs 路径下提供 Jamdesk 文档。选择以下两种设置之一,效果相同。

前提条件

  • 已部署在 Vercel 上的项目
  • 你的 Jamdesk 子域名(可在仪表板设置中找到),并已启用 Host at a subpath
  • 已在仪表板中注册并验证的自定义域名

选项 A:vercel.json 重写

使用自定义子路径而非默认的 /docs?请将下方代码片段中每条规则的 source 和 destination 里的 /docs 替换为你的子路径。在仪表板中重命名子路径后重新部署。此文件不会为你自动生成。

重写可以改变请求的去向,但无法添加请求头。因此每个目标地址都带有一个公开标记 ?jd_proxy=1,它告诉 Jamdesk 该请求经过了你的代理。Jamdesk 在渲染前会将其移除,访问者不会看到它。

在项目根目录创建或编辑 vercel.json,将 YOUR_SLUG 替换为你的 Jamdesk 子域名:

vercel.json
{
  "rewrites": [
    { "source": "/docs", "destination": "https://YOUR_SLUG.jamdesk.app/docs?jd_proxy=1" },
    { "source": "/docs/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/docs/:path*?jd_proxy=1" },
    { "source": "/_next/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
    { "source": "/_jd/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_jd/:path*?jd_proxy=1" }
  ]
}

/_next/:path* 不需要标记。它是 Jamdesk 从不拦截的静态资源路径。非 Next.js 站点本身没有 /_next/ 文件,该路径下的所有请求都会转发给 Jamdesk。

对于 Next.js 项目,同样的四条规则也可写在 next.config.js 中:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      { source: "/docs", destination: "https://YOUR_SLUG.jamdesk.app/docs?jd_proxy=1" },
      { source: "/docs/:path*", destination: "https://YOUR_SLUG.jamdesk.app/docs/:path*?jd_proxy=1" },
      { source: "/_next/:path*", destination: "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
      { source: "/_jd/:path*", destination: "https://YOUR_SLUG.jamdesk.app/_jd/:path*?jd_proxy=1" },
    ];
  },
};

export default nextConfig;

同时转发根目录文件

robots.txt、sitemap.xml、llms.txt 和 llms-full.txt 位于域名根目录,不在 /docs 下。搜索引擎和 AI 代理需要它们来发现你的文档。如果域名根目录由 Jamdesk 托管,请再添加四条规则:

vercel.json
{
  "rewrites": [
    { "source": "/robots.txt", "destination": "https://YOUR_SLUG.jamdesk.app/robots.txt?jd_proxy=1" },
    { "source": "/sitemap.xml", "destination": "https://YOUR_SLUG.jamdesk.app/sitemap.xml?jd_proxy=1" },
    { "source": "/llms.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms.txt?jd_proxy=1" },
    { "source": "/llms-full.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms-full.txt?jd_proxy=1" }
  ]
}

如果你的营销站点已在根目录提供自己的 robots.txt 或 sitemap.xml,请合并两者,而不是用其中一个覆盖另一个。

部署并验证

使用 vercel --prod 部署(或推送到已连接的 Git 仓库)。打开 https://yoursite.com/docs 并查看页面源代码,检查两点:

  1. <link rel="canonical"> 指向你的域名,而非 YOUR_SLUG.jamdesk.app
  2. 不存在 <meta name="robots" content="noindex">

如果其中任一项不正确,说明某条重写缺少 ?jd_proxy=1 标记。参见为什么标记很重要。

选项 B:Edge Middleware

在 Edge Middleware 中设置 X-Jamdesk-Forwarded-Host 请求头,代替使用标记。两种信号效果等同。若希望将路由逻辑保留在你已维护的代码中,请选择此方案。

在项目根目录创建 middleware.ts 文件,将 YOUR_SLUG 替换为你的 Jamdesk 子域名:

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const JAMDESK_HOST = 'YOUR_SLUG.jamdesk.app';

export function middleware(request: NextRequest) {
  const url = request.nextUrl;
  const destination = new URL(url.pathname + url.search, `https://${JAMDESK_HOST}`);

  // Clone headers and tell Jamdesk which domain the visitor actually used.
  const headers = new Headers(request.headers);
  headers.set('X-Jamdesk-Forwarded-Host', url.hostname);

  return NextResponse.rewrite(destination, {
    request: { headers },
  });
}

export const config = {
  matcher: ['/docs', '/docs/:path*', '/_jd/:path*'],
};

若使用自定义子路径,请将上方 matcher 中的 /docs 和 /docs/:path* 替换为你配置的子路径。

不要将 /_next/:path* 添加到 matcher 中。 Middleware 在 Vercel 的文件系统检查之前运行。匹配 /_next/ 会将你自己站点的 JavaScript 和 CSS 发送给 Jamdesk,从而破坏样式。下一步会以安全的方式路由 /_next/。

将文件命名为 middleware.ts,并导出一个名为 middleware 的函数。Next.js 16 建议将其重命名为 proxy.ts,但 Vercel 生产环境目前不会调用 proxy.ts,重命名会静默地使其失效。

在 vercel.json 中路由资源和根目录文件

Jamdesk 页面从 /_next/ 加载其代码包。robots.txt 等根目录文件位于上方 matcher 之外。请用重写规则路由这两类文件。重写在文件系统检查之后运行,因此你自己的构建产物优先,只有你的站点无法提供的请求才会转发给 Jamdesk:

vercel.json
{
  "rewrites": [
    { "source": "/_next/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
    { "source": "/robots.txt", "destination": "https://YOUR_SLUG.jamdesk.app/robots.txt?jd_proxy=1" },
    { "source": "/sitemap.xml", "destination": "https://YOUR_SLUG.jamdesk.app/sitemap.xml?jd_proxy=1" },
    { "source": "/llms.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms.txt?jd_proxy=1" },
    { "source": "/llms-full.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms-full.txt?jd_proxy=1" }
  ]
}

根目录文件的目标地址带有 ?jd_proxy=1。Middleware 不会对它们运行,且重写无法设置请求头。仅当 Jamdesk 托管你的域名根目录时才需添加这些规则。如果你的站点提供自己的 robots.txt 或 sitemap.xml,请改为合并。

部署并验证

使用 vercel --prod 部署(或推送到已连接的 Git 仓库)。打开 https://yoursite.com/docs 并查看页面源代码,检查两点:

  1. <link rel="canonical"> 指向你的域名,而非 YOUR_SLUG.jamdesk.app
  2. 不存在 <meta name="robots" content="noindex">

如果其中任一项不正确,说明 X-Jamdesk-Forwarded-Host 请求头未能到达 Jamdesk。请确认 /docs 已在 matcher 中。

为什么需要标记

Jamdesk 需要一个信号,以区分请求是经过你的代理到达的,还是直接访问你的 *.jamdesk.app 子域名。?jd_proxy=1 标记和 X-Jamdesk-Forwarded-Host 请求头承载了这一信号。Jamdesk 将两者视为等效。任一信号都会完成三件事:

  • 确认你已注册的域名
  • 将 canonical、Open Graph 和站点地图链接指向该域名
  • 让仅自定义域名功能区分代理流量与直接访问

信号缺失时会悄然失败:页面仍能正常渲染。若未注册自定义域名,页面会带有 noindex 和子域名 canonical;若已注册自定义域名,"仅自定义域名"检查将失败。403 表示信号存在,但所指定的域名对该项目未注册或未激活。

故障排除

你的 matcher 很可能包含了 /_next/:path*。Middleware 在文件系统检查之前运行,会将你自己应用的资源路由到 Jamdesk。请从 matcher 中移除 /_next/,改为通过 vercel.json 路由。参见方案 B。

在 https://yoursite.com/docs 查看页面源代码。若存在 <meta name="robots" content="noindex"> 标签,说明标记和请求头都未到达 Jamdesk。在方案 A 中,确认每条重写的目标地址都以 ?jd_proxy=1 结尾(/_next/:path* 除外)。在方案 B 中,确认 /docs 已在 middleware matcher 中。仅靠 vercel.json 重写无法设置请求头。

标记或请求头已到达 Jamdesk,但所指定的域名不会为该项目提供服务:

  1. 确认你的域名已在 Jamdesk 仪表板中注册
  2. 完成 DNS 验证(TXT 记录)
  3. 检查该域名是否映射到正确的项目,且已标记为激活
  4. 在方案 B 中,确认 middleware 设置的请求头是访问者使用的域名,而非预览 URL

请确认你的重写(或 matcher)同时覆盖了 /docs 和 /docs/:path*。缺少通配符时,嵌套页面会失败。

请同时路由 /_jd/:path* 和 /_next/:path*。/_jd/ 提供 Jamdesk 字体、图片和品牌资源;/_next/ 提供文档代码包。缺少任一项都会破坏布局。

请检查文件是否位于项目根目录,且命名为 middleware.ts(而非 proxy.ts),并导出名为 middleware 的函数。Next.js 16 的弃用警告推荐使用 proxy.ts,但 Vercel 生产环境不会调用它。

请检查目标 URL 是否使用 https:// 并指向 jamdesk.app,而不是指回你自己的域名。

该检查会在你的正式域名上请求 /_jd/preflight,并检查到达 Jamdesk 的内容。“未标识自身”意味着某条重写到达 Jamdesk 时缺少标记或请求头。请重新检查每条重写的目标地址(方案 A)或你的 middleware 请求头(方案 B)。参见仅自定义域名。

接下来呢?

仅自定义域名

阻止你的子域名直接响应

自定义域名

验证 DNS 并排查问题

子路径托管

在 /docs 下提供文档