轻屿课表文档

构建与部署

本地构建、静态导出验证,以及 Cloudflare Pages 上线步骤。

本站是独立的 Next.js + Fumadocs 应用,产物为纯静态文件,不依赖 Node 运行时服务器。

本地开发

在仓库根目录进入 site/:

cd site
pnpm install
pnpm dev

开发服务器默认在 http://localhost:3000。修改 content/docs/ 下的 MDX 后会热更新。

静态构建

在仓库根目录执行(下面两条等价,任选其一):

# 方式一:先进目录再构建
cd site
pnpm build
cd ..            # 回到仓库根,下一步的 serve 命令要用
# 方式二:直接指定目录,不用 cd
pnpm --dir site build

next.config.mjs 已开启 output: 'export',构建结果写入 site/out/。产物特点:

  • 每个页面都有对应的 .html 与同名 .txt(纯文本,便于 AI agent 抓取)
  • 全文搜索索引在 out/api/search/,浏览器端计算,无需服务端
  • 可直接丢到任意静态托管(Cloudflare Pages、Nginx、GitHub Pages 等)

本地预览导出结果(在仓库根目录执行,不要留在 site/ 里,否则会去找 site/site/out):

# 任意静态服务器即可,例如:
npx serve site/out

Cloudflare Pages 构建时必须设置环境变量 NODE_VERSION=22。Fumadocs 要求 Node ≥ 22,而 Pages 默认 Node 版本偏低,不设会直接构建失败。

上线到 Cloudflare Pages

目标域名:docs.163366.xyz。

在 Cloudflare Pages 中新建项目,连接 GitHub 仓库 Mutx163/mikcb。

配置构建:

项值
Root directory(根目录)site
构建命令corepack enable && pnpm install --frozen-lockfile && pnpm build
输出目录out

环境变量 NODE_VERSION 建议写明确版本(如 22.16.0)而不要写 22,理由见下。

为什么必须改 Root directory(2026-09-13 真实故障)

原配置是「根目录留空(= 仓库根)+ 命令 cd site && pnpm i && pnpm build」, 在 Cloudflare 上直接失败:

Executing user command: cd site && pnpm install && pnpm build
No preset version installed for command pnpm
Please install a version by running one of the following:
asdf install nodejs 22.22.0
or add one of the following versions in your config file at /opt/buildhome/.tool-versions
nodejs 14.21.3 / 16.20.2 / 18.17.1 / 20.19.0 / 22.16.0
Failed: Error while executing user command. Exited with error code: 126

根因有两层,第二层才是这次真正踩到的那个:

  1. 根目录留空 = 整个仓库被当成根目录。 官方文档写得很直白: 「根目录不填时 Cloudflare 假定整个 git 仓库就是根目录;像 monorepo 这种 一个仓库里多个项目的情况必须指定根目录」 (Build configuration)。 而本仓库的 package.json 与 pnpm-lock.yaml 都在 site/ 下,根目录什么都没有。

  2. Node 被解析到了预设表之外的版本。 日志里 Node 装的是 22.22.0, 但 asdf 列出的可用预设只有 … 20.19.0 / 22.16.0,22.22.0 不在其中。 CF 构建镜像的工具(含 pnpm)是按 Node 预设配套提供的,Node 落在预设之外 就一并拿不到 pnpm,于是 pnpm 命令根本不存在 → 退出码 126。 这就是 NODE_VERSION=22(会解析成最新的 22.x)比写死 22.16.0 更危险的原因。

另外,pnpm 本身在镜像里是预装的:v2 镜像默认 8.7.1、v3 镜像默认 10.11.1, 覆盖变量是 PNPM_VERSION(Build image)。 但 v3 镜像的已知限制里明确写着「不支持按 pnpm-lock.yaml 探测 pnpm 版本」, 所以不要指望它自动挑对版本。

因此推荐:根目录设成 site,并用 corepack enable 让 pnpm 版本由 site/package.json 的 "packageManager": "pnpm@11.22.0" 决定。这与生成 pnpm-lock.yaml 的版本严格一致,避免用镜像默认的 10.x 去装 11.x 生成的锁文件 而触发 --frozen-lockfile 失败。

备选(必须保留「根目录 = 仓库根」时): 保持命令 cd site && pnpm install && pnpm build、输出 site/out, 然后加两个环境变量:NODE_VERSION=22.16.0(落在预设表内)与 PNPM_VERSION=<与 lockfile 生成版本一致的值>。

已知问题:__next.* 预取载荷 404(Cloudflare Pages 路径匹配差异)

Next.js 静态导出把 RSC 预取载荷落盘为带字面 $ 的目录嵌套形式:

site/out/docs/guide/quick-start/__next.docs/$oc$slug/__PAGE__.txt

而客户端预取请求用的是点号压平形式:

GET /docs/guide/quick-start/__next.docs.$oc$slug.__PAGE__.txt

site/public/_redirects 里有两条把点号形式 200 重写到嵌套形式的规则。

⚠️ 已复测:这两条规则是空转的,修不了这个 404(2026-09-13,新构建上线后实测)。

请求结果
/docs/guide/quick-start/__next.docs.txt200 ✅
/docs/guide/quick-start/index.txt200 ✅
…/__next.docs/$oc$slug/__PAGE__.txt(字面 $)404 ❌
…/__next.docs/%24oc%24slug/__PAGE__.txt(百分号编码)404 ❌
…/__next.docs.$oc$slug.__PAGE__.txt(点号压平)404 ❌
/__next.!KGhvbWUp/__PAGE__.txt404 ❌

关键事实:那个 $oc$slug/__PAGE__.txt 文件确实在构建产物里(site/out/… 下能直接找到),但 Cloudflare 不服务 $oc$slug/ 这条路径。所以规则把一个 404 重写成另一个 404,不可能生效。原注释里「嵌套形式 → 200」很可能是本地 python3 -m http.server --directory site/out 测出来的。本地文件服务器不介意 $,Cloudflare 介意。

影响面很小:软导航正常(走 index.txt / __next.*.txt,都是 200),只是控制台 多一条预取 404、预取退化为按需拉取。

要真正修掉只有两条路(都未做):a) 用 Pages Function + ASSETS 绑定自己做 点号形式→$ 路径的映射(不再指望静态服务器命中);b) 向 Next / Cloudflare 反馈「$ 目录名在 Pages 静态资源上 404」。别再往 _redirects 里加规则, 重写目标本身不可达。

部署完成后,在 Pages 项目的自定义域中添加 docs.163366.xyz,按提示完成 DNS。

之后主分支上对 site/ 的推送会自动触发重新构建与发布。

上线检查清单(换域名 / 长期间未部署后逐条核对)

2026-09 换域名(mutx.ccwu.cc → 163366.xyz)时踩过的坑,逐条对应一个真实故障:

检查项怎么查出现过的问题
文档站是否真的重新构建过curl -s https://docs.163366.xyz/ | grep canonical源码已加 canonical/sitemap/llms.txt,但 Pages 仍是旧构建,全部 404
文档站 sitemap 可达curl -sI https://docs.163366.xyz/sitemap.xml构建过期时为 404,24 篇文档页对搜索引擎零入口
旧域名 301 是否一步到 httpscurl -sI https://mutx.ccwu.cc/ | grep -i location曾一度跳到 http://163366.xyz/(明文中间跳),应为 https:// 直达
www 是否解析getent hosts www.163366.xyz无记录(NXDOMAIN)。若 GSC 属性建在 www 上,站点地图永远读不到
站点地图能否被读curl -s https://163366.xyz/sitemap.xml | python3 -c 'import xml.dom.minidom,sys;xml.dom.minidom.parseString(sys.stdin.read());print("ok")'文件本身一直合法,问题在 GSC 侧属性状态

换域名后必做:GSC 里对应重新验证属性(旧验证随域名失效)、重新提交站点地图、 核对属性是「网域」还是「网址前缀」且与 301 目标一致。

维护本站时的已知坑

改这个站之前建议先读一遍,都是实际踩过的:

  1. fumadocs-mdx 15 生成的 .source/ 没有 index.ts
    从 @/.source/server 导入,不要写 @/.source。

  2. MDX 里的裸 < 会被当成 JSX 标签
    例如版本区间 >=3.12.2 <4.0.0,<4.0.0 要用反引号包住,或改写为 &lt;。

  3. Step / Steps 不在默认 MDX 组件里
    已在根目录 mdx-components.tsx 手动注册;新增同类组件时同样要挂进去。

  4. pnpm 11 默认不跑依赖的构建脚本
    esbuild 等需要构建产物的包,已在 pnpm-workspace.yaml 的 allowBuilds 中放行。新增需要 postinstall 的依赖时同步更新该文件。

  5. .source/、.next/、out/ 不要提交
    已由 site/.gitignore 排除;out/ 由 Cloudflare 在构建机上生成。

  6. app/favicon.ico 会覆盖 public/favicon.ico
    Next 把 app/favicon.ico 当作文件约定(等价于一条 /favicon.ico 路由), 路由优先于 public/ 静态文件。2026-09-12 曾在 public/ 下新增一份 favicon.ico,它从未被服务过(out/favicon.ico 始终等于 app/favicon.ico)。 要换图标请改 app/favicon.ico;public/ 下只放不被 app 约定接管的资源 (favicon-48.png 这类带尺寸的 PNG 不受影响)。

相关

  • 内容目录:仓库 site/content/docs/
  • 贡献约定:参与贡献
  • 站点仓库入口:GitHub

On this page