部署与域名
这个仓库有三个构建产物,当前复用两个 Cloudflare Pages 项目:
| 产物 | 是什么 | 构建 | 输出 |
|---|---|---|---|
| 文档站(就是本站) | VitePress,纯文档 | npm run docs:build | docs/.vitepress/dist/ |
工作站 apps/station | 带插件的 CMS 宿主 | npm run -w @aio/station build | apps/station/out/ |
Story Router apps/router | 搜索结果中转与运维入口 | npm run cloudflare:router:build | apps/router/dist/,包含 /docs/ 文档 |
文档与 Router 共用既有项目:Router 根目录保留运维页,/open 保留剧情/精灵中转, /docs/ 承载完整文档;Station 仍独立。无需另建文档项目、绑定或付费资源。 具体主机名不写进仓库——这套框架要能当模板用,域名是部署方的事。 下面一律用 <文档域名> / <业务域名> 指代,实际值填在托管平台的配置里。
文档站
本地
npm run docs:dev # http://localhost:5173
npm run docs:build
npm run docs:preview # 预览构建产物,**不要**去双击 dist/index.html别双击 dist/index.html
产物里的资源引用是站内绝对路径(单独预览为 /assets/…,Cloudflare 合包为 /docs/assets/…)。 用 file:// 打开时浏览器会把 /assets/… 解析到文件系统根目录, 于是 CSS 与 JS 全部 404,页面看起来像坏了。要看构建结果就用 npm run docs:preview。
Cloudflare Pages(当前文档发布)
构建时将 DOCS_ORIGIN 设为现有 Router 的 origin,例如 https://<Router域名>, 不含 /docs/。该值用于 sitemap 与分享链接,避免发布示例域名。项目名仍由部署环境提供。
export DOCS_ORIGIN=https://<Router域名>
export AIO_CLOUDFLARE_ROUTER_PROJECT=<已有Router项目名>
export AIO_CLOUDFLARE_BRANCH=main
npm run cloudflare:docs:build
npm run cloudflare:docs:verify
npm run cloudflare:router:deployPowerShell 使用 $env:DOCS_ORIGIN='https://<Router域名>' 等形式设置同名环境变量。
cloudflare:docs:build 与 cloudflare:router:build 是同一完整发布管线:先重新生成 Router 及函数表,再以 /docs/ 为 base 构建当前文档,替换 dist/docs/,最终上传一个 完整 Router 包。文档更新与普通 Router 更新都使用该管线,避免后续部署丢失文档或混入 上次构建的残留。story-router:build 保留为路由库的独立开发/测试命令。
上线后检查 /docs/、任意文档深链、搜索结果,以及 /docs/build-info.json 中的 sourceCommit 与当前 main 一致、sourceDirty=false。同时检查 /open 和契约 JSON。 构建成功只证明产物生成;公开正文、资源、导航与搜索通过后才算文档发布完成。
回滚以同一 Router 项目的上一完整部署为单位,文档和 Router 一起恢复;勿单独覆盖根目录 为纯文档产物,那会丢失中转入口。此过程与 GitHub Actions 的账户启动状态相互独立。
GitHub Pages(保留的旧发布接线)
以下是原有 workflow 的使用记录,不是当前 Cloudflare 文档生产入口;其账户或 Pages 门禁仍单独跟踪,不以本地构建或 Cloudflare 发布来标记它已通过。
.github/workflows/docs.yml 在 push 到 main 时构建并发布。
自定义域名从仓库变量取,不入库:Settings → Secrets and variables → Actions → Variables → 新建 DOCS_DOMAIN,值填 <文档域名>(裸主机名,不带 https://)。 workflow 会据此写出产物里的 CNAME,并把 DOCS_ORIGIN 传给构建(sitemap 与 og:url 用它)。不设也能跑——站点照常发到 *.github.io,只是没有自定义域名。
上线前要人工做两件事,都自动不了
一、开 Pages。 仓库 Settings → Pages → Source 选 GitHub Actions。
workflow 里带了 enablement: true,但 2026-08-21 实测在本组织下不管用:
不带 enablement → Get Pages site failed … Not Found
带 enablement:true → Create Pages site failed … Resource not accessible by integrationGITHUB_TOKEN 无权创建 Pages 站点。那一条留着是因为站点开好之后它是空操作, 在权限更宽的 fork 里还能省掉这一步。
二、配 DNS。
DNS 那条记录
在你的 DNS 上给 <文档域名> 加一条 CNAME,指向 <组织名>.github.io.(末尾那个点别丢)。
然后在仓库 Settings → Pages 里确认自定义域名已识别、并勾上 Enforce HTTPS。 证书签发要等几分钟。
这是「发布类 workflow 一律手动」的一个有意的例外
本仓库沿用兄弟仓库的分工:检查类 workflow 允许 push 触发,发布类一律手动。 文档站在这里被判为例外,理由有两条:
- 落后的文档比没有文档更糟——它会让人按一份不成立的描述去改代码;
- 与 APK 发版不同,文档发布是幂等且可逆的:没有版本号,没有用户在安装 什么东西,推错了改回来再推一次即可。
不接受这个判断的话,把 docs.yml 的 on.push 去掉就变回纯手动。
Cloudflare Pages
AIO 的两个业务产物使用两个独立的 Cloudflare Pages 项目:
| 项目 | 目录 | 构建 | 静态输出 | Functions |
|---|---|---|---|---|
| Story Router | apps/router/ | npm run cloudflare:router:build | dist/ | functions/open.js |
| 工作站 | apps/station/ | npm run cloudflare:station:build | out/ | functions/embed/[capability].js |
两个项目都以各自目录为上传工作目录。Cloudflare Pages 会从该目录的 functions/ 生成文件路由;因此 /open、/embed/<capability> 的动态入口不再依赖 EdgeOne。
npm run cloudflare:router:build
npm run cloudflare:router:verify
npm run cloudflare:station:buildRouter 构建会在 dist/_headers 写出跨域读取与缓存头;Station 的嵌入页由 Pages Function 返回,静态 out/embed/ 在构建后被移入函数包,避免绕过准入与 CSP。
部署项目名、Cloudflare 登录状态、KV/R2 binding id 与私密变量均保持在控制台或部署环境:
- Router:
AIO_READER_BASE_URL、AIO_ADV_BASE_URL、AIO_SPRITE_BASE_URL、AIO_ADV_RENDERER、AIO_ADV_HANDOFF_ENABLED; - Station:
AIO_KV(KV)、AIO_TAKEDOWN_R2(R2)、AIO_EMBED_ANCESTORS、AIO_EMBED_CACHE_SECONDS。
R2 的 Worker binding 读用于下架清单,读不到时仍 fail-closed;站点配置继续用 KV。 仓库不写项目名、账号、token 或 binding id。上传只在显式执行 npm run cloudflare:router:deploy / npm run cloudflare:station:deploy 时发生。
工作站 apps/station
npm run -w @aio/station build # → apps/station/out/output: 'export' 静态导出,交付到独立的 Cloudflare Pages Station 项目。
两条命令都带 --webpack
Next 16 起 Turbopack 是缺省打包器,而 packages/ 里的相对 import 带 .js 后缀 (TS 写 ESM 的正确写法),Turbopack 目前没有 webpack extensionAlias 的对应物 ——2026-08-21 实测拿掉之后 51 个 Can't resolve './xxx.js' 全部复现。 等 Turbopack 能表达这条解析规则再迁。
静态导出与 KV 的矛盾
静态页读不到 KV,而站点配置(插件开关、SEO、下架清单)住在 KV 里。 后台改了开关,静态页不会知道。当前建议:
- 内容页保持静态(爬虫要的就是它,而且最快);
- 动态行为(开关、下架判定、鉴权)走 Cloudflare Pages Functions;
- 下架靠 Function 每请求现读强一致清单兜住,不等重建(铁律 11)。 规则引擎与 purge 都和重建同速——而且函数只在没有静态产物的路径上触发。 这一条目前只有嵌入面做到了,内容页待拍板。
旧平台分析保留为历史材料;当前部署以本节 Cloudflare Pages 配置为准。
资源面
素材不在这两个产物里,它们在独立对象存储 + CDN 资源面,前端经清单按 ref 取。
清单可以离线生成、离线校验,不需要桶权限:
python3 tools/build-manifest.py ASSETS_DIR \
--universe a --kind sprite \
--pattern '(?P<id>\d+)/(?P<variant>[a-z_]+)\.' \
--ref '{id}/{variant}' --prefix 'sprite/' \
--out manifest.mr.sprite.json桶开好之后直接上传即可。详见资源面。
历史限额记录
旧 EdgeOne Pages 的限额不适用于当前 Cloudflare Pages 部署,保留的旧数字仅用于解释历史设计,不能作为当前上线依据。当前项目的运行时、函数和资源限额应以 Cloudflare 控制台与官方文档为准。