Yeji / Software Architecture / 2026.07
页集软件架构说明书
一份面向产品、研发、运维与内容维护者的架构基线:解释页集如何组织 HTML 文档、如何在边缘运行、 当前系统为什么保持静态优先,以及下一阶段怎样在不破坏简单性的前提下演进。
页集是一个由 Next.js / React 应用壳、独立静态 HTML 文档、 TypeScript 文档索引和 Cloudflare Worker 边缘入口组成的只读知识书架; 搜索与筛选在浏览器完成,当前不依赖数据库和对象存储。
文档控制与阅读约定
本文是“现状基线 + 目标建议”的组合文档。凡是尚未在代码中实现的内容都会明确标记,不能直接作为已上线能力对外承诺。
来自代码与配置
对仓库、构建脚本、测试、运行入口与托管声明的直接归纳,可作为当前系统说明。
可执行的演进方向
用于规划、评审和拆分任务,不代表已经承诺时间或已经具备对应服务等级。
需要验证或治理
按影响与发生可能性排序,包含触发条件、缓解动作和建议责任角色。
| 项目 | 定义 | 本版内容 |
|---|---|---|
| 系统名称 | 页集(Yeji HTML Document Archive) | 集中发布、检索与阅读 HTML 文档的响应式知识书架 |
| 主要读者 | 产品负责人、研发、运维、内容维护者、架构评审者 | 共同理解边界、依赖、风险、质量门禁与演进顺序 |
| 覆盖视图 | 上下文、容器、组件、运行时、数据、安全、质量、部署 | 接近 C4 + 运行视图 + 风险台账的组合 |
| 不覆盖 | 业务内容正确性、托管平台内部实现、组织预算与排期 | 这些内容需要由对应责任方另行确认 |
| 更新触发 | 新增运行时依赖、启用 D1/R2、引入登录、改变发布链路 | 任一条件发生时,应同步更新本文和相关 ADR |
当前系统规模小、读多写少、内容以版本库为来源,静态优先是合理选择。架构治理重点不是提前引入更多基础设施, 而是先解决内容可信、索引一致、路由唯一和发布可验证。
架构执行摘要
页集的设计本质是“应用负责发现,文档负责阅读,边缘负责交付”。它用较低的运行复杂度换取高可移植性和高阅读稳定性。
静态内容是主资产
富文档以完整 HTML 文件进入 public/docs/,样式和交互可以随文档独立演进;
即使应用层功能退化,静态文件仍具备直接交付的基础。
边缘运行且没有业务数据库
Worker 接住应用请求与图片优化,文档索引在构建时进入应用。没有网络数据库查询, 因而读路径短、部署面小、日常运维成本低。
两条阅读路径并存
高价值长文走 /docs/*.html 独立页面;普通索引条目走
/docs/[slug] 通用模板。灵活性更高,但会带来路由、导航和样式一致性的治理成本。
把手工约定变成机器校验
文档文件、索引记录和页面链接目前依赖人工同步。下一阶段最有价值的改进是唯一清单、 路由一致性测试、内容安全检查和最小可观测性。
架构适配度(定性评估)
| 维度 | 当前适配度 | 判断依据 | 主要缺口 |
|---|---|---|---|
| 阅读与分发 | 静态文档、响应式布局、目录、打印、阅读进度 | 独立文档之间的设计语言尚未完全统一 | |
| 运行成本 | 无业务数据库、边缘交付、依赖少 | 仍需建立资源量与流量基线 | |
| 可维护性 | 类型、Lint、测试和 CI 已存在 | 手工索引、正则导入脚本、双路由重复 | |
| 安全治理 | 只读、无持久化输入、密钥不入库 | 同源 HTML 脚本信任、CSP 与依赖扫描未形成门禁 | |
| 可观测性 | 开发日志路径已配置 | 没有应用级错误率、性能、文档 404 与发布健康指标 |
在没有多人在线编辑、权限化管理、业务分析或大媒体资产需求之前,不建议仅为“架构完整”而启用 D1、R2 或后台管理系统。
系统范围与上下文
页集位于内容作者与读者之间:作者通过版本库交付内容,读者通过浏览器访问,CI 和托管平台构成发布控制面。
C4 Level 1 · 系统上下文
箭头表示内容或请求的主要方向;虚线能力未在当前业务路径启用。
支撑系统:GitHub Actions 在推送和 Pull Request 上执行质量检查;OpenAI Sites / Cloudflare 提供托管与边缘运行环境。
参与者与责任
| 参与者 / 系统 | 核心目标 | 与页集的交互 | 信任级别 |
|---|---|---|---|
| 读者 | 快速找到并稳定阅读内容 | 发送匿名 GET 请求;在浏览器内搜索、筛选、打印 | 不可信输入边界 |
| 内容作者 | 发布完整、可维护的长文 | 提交 HTML 和资源;补齐元数据 | 受控贡献者 |
| 研发 / 维护者 | 保持系统可构建、可发布、可回滚 | 变更应用代码、脚本、配置和质量门禁 | 高信任角色 |
| GitHub Actions | 阻断不合格变更 | 执行安装、Lint、类型检查、构建与页面测试 | 自动化控制面 |
| OpenAI Sites / Cloudflare | 托管与边缘执行 | 提供 Worker、ASSETS、IMAGES 及可选资源绑定 | 平台依赖 |
- 首页、文档索引和浏览器交互
- 通用阅读器与独立 HTML 长文
- Worker 请求入口和图片优化路由
- 内容导入脚本、测试与 CI 配置
- 可选的 D1 / R2 接入骨架
- 原始研究资料与内容事实核验
- GitHub、Sites、Cloudflare 的平台内部实现
- 域名、访问策略和组织身份的管理后台
- 终端浏览器、打印机与 PDF 阅读软件
- 未来可能接入的分析、搜索或内容管理服务
架构目标与质量属性
架构优先级从“内容可达、可读”开始,再到可维护、安全与扩展。所有目标都需要可验证的场景,而不仅是原则性表述。
| 质量属性 | 优先级 | 当前机制 | 验证场景 |
|---|---|---|---|
| 可发现性 | P0 | 标题、主题、摘要检索;分类筛选;更新时间与标题排序 | 读者在一个入口内定位已知或模糊目标文档 |
| 可读性与无障碍 | P0 | 语义化结构、跳转链接、原生控件、响应式、打印、减弱动态效果 | 键盘、窄屏、打印和辅助技术用户可完成核心任务 |
| 可用性 | P0 | 静态资产 + 边缘交付,无业务数据库依赖 | 单篇文档访问不被数据服务故障阻断 |
| 性能 | P1 | 边缘 Worker、静态文件、图片优化、客户端内存搜索 | 首页和长文在常见网络与移动设备上快速可用 |
| 可维护性 | P1 | TypeScript、ESLint、测试、CI、文档导入脚本 | 新增文档后能自动发现文件、索引、链接和结构错误 |
| 安全与隐私 | P1 | 只读产品、无业务输入持久化、密钥不入库、安全回跳校验 | 恶意请求、恶意 HTML 或错误身份回跳不能扩大权限 |
| 可观测性 | P2 | 开发工具日志路径 | 发布后能在分钟级发现错误率、404 或性能退化 |
建议的服务目标
建议目标 以下为下一阶段可采用的度量基线,当前仓库没有证据表明这些目标已被持续测量。
独立 HTML 允许每篇长文获得最佳表达,但会削弱全站一致性和集中治理;统一模板更容易维护, 却限制复杂图表与专属交互。当前“双路径”是有意识的折中,前提是建立明确的选路标准。
技术栈与关键约束
应用采用 Next.js 编程模型,但生产运行形态是由 vinext 与 Vite 构建的 Cloudflare Worker,而不是传统常驻 Node.js 服务器。
| 层级 | 技术 / 版本 | 职责 | 架构约束 |
|---|---|---|---|
| UI / 应用 | Next.js 16.2.12、React 19.2.6 | App Router、Server / Client Component、元数据、状态页 | 路由和组件需兼容 vinext 支持面 |
| 构建 | vinext 0.0.50、Vite 8.0.13 | 把 Next.js 应用构建为边缘可运行产物 | 输出应为 Worker 兼容 ESM,不能依赖常驻 Node 进程 |
| 边缘运行 | Cloudflare Vite Plugin 1.37.1、Wrangler 4.92.0 | 本地模拟、绑定注入、Worker 运行和静态资源 | 使用 nodejs_compat,平台绑定由环境提供 |
| 语言 | TypeScript 5.9.3、Node ≥ 22.13.0 | 应用、Worker、数据库适配与构建配置 | strict 开启;模块解析为 bundler |
| 数据预留 | Drizzle ORM 0.45.2、Drizzle Kit 0.31.10 | 未来 D1 表结构和类型化访问 | 当前 schema 为空,D1 绑定未启用 |
| 样式 | 全局 CSS + 文档内联 CSS;Tailwind 构建依赖已存在 | 应用壳统一样式和独立长文专属样式 | 当前页面主要使用手写 CSS,不应假设已有组件库 |
边缘兼容
运行时代码应使用 Web API 或经验证的兼容接口;避免文件系统、长连接进程与本地磁盘假设。
内容可移植
直达 HTML 使用站内相对路径,不依赖作者机器绝对路径;理想情况下无外部运行依赖。
配置与密钥分离
.openai/hosting.json 只声明项目和逻辑资源;运行时密钥应由托管环境注入。
package.json 与 package-lock.json 固定了当前依赖组合,有利于可重复构建;
但 vinext 和平台插件仍处于快速演进区,应通过升级分支和完整检查验证兼容性。
逻辑分层架构
系统可以划分为体验、内容领域、应用运行、边缘适配和平台资源五层。业务依赖从上向下,内容模型不应反向依赖具体托管实现。
逻辑分层图
横切能力包括可访问性、类型、测试、CI、安全与文档规范。
分层规则
- 文档元数据属于内容领域,不应散落在 UI 组件中。
- 首页交互只消费
DocumentItem[],不直接读取文件系统。 - 平台绑定通过 Worker / 数据适配层进入,不应出现在展示组件中。
- 独立 HTML 可以拥有专属表现,但必须遵循安全、链接和无障碍底线。
- 数据库和对象存储是需求触发的可选能力,不是默认依赖。
- 构建与测试对内容文件同样负责,不能只验证 React 应用。
- 通用阅读器与独立长文共享导航语义,而非强制共享全部 CSS。
- 任何跨层捷径都应通过 ADR 记录原因、期限和退出条件。
容器与组件设计
这里的“容器”指可独立理解的运行或交付单元,不等同于 Docker 容器。当前主要由应用壳、静态文档集与边缘入口组成。
| 容器 / 组件 | 位置 | 运行位置 | 责任 | 状态 |
|---|---|---|---|---|
| 根布局与元数据 | app/layout.tsx |
边缘服务端 | 语言、动态 metadataBase、图标、Open Graph、Twitter 元数据 | 在用 |
| 首页服务端入口 | app/page.tsx |
边缘服务端 | 把静态文档索引传给客户端组件 | 在用 |
| 首页交互 | app/components/HomeClient.tsx |
浏览器 | 搜索、筛选、排序、移动菜单和发布说明对话框 | 在用 |
| 文档目录 | app/lib/documents.ts |
构建期 / 服务端 / 客户端数据 | 定义 DocumentItem 与当前全部文档元数据 | 唯一手工索引 |
| 通用阅读器 | app/docs/[slug]/page.tsx |
边缘服务端 + 浏览器 | 静态参数、页面元数据、通用正文模板、下一篇导航 | 在用 |
| 阅读器交互 | ReaderActions.tsx |
浏览器 | 滚动进度、复制链接、打印与提示 | 在用 |
| 独立 HTML 文档 | public/docs/*.html |
浏览器 / 静态资产 | 完整长文、专属 CSS、目录与页面内交互 | 3 篇 |
| Worker 入口 | worker/index.ts |
Cloudflare Worker | 图片优化分流,其余请求交给 vinext App Router handler | 在用 |
| 身份辅助 | app/chatgpt-auth.ts |
边缘服务端 | 读取身份头、要求登录、安全构造登录/登出回跳地址 | 当前路由未调用 |
| D1 数据适配 | db/index.ts、db/schema.ts |
边缘服务端 | 可选 Drizzle D1 连接;无绑定时显式报错 | 未启用 |
源代码组件树
app/ ├─ layout.tsx # 站点元数据与根布局 ├─ page.tsx # 首页服务端入口 ├─ components/ │ └─ HomeClient.tsx # 搜索、筛选、排序与首页交互 ├─ docs/[slug]/ │ ├─ page.tsx # 通用阅读器 │ └─ ReaderActions.tsx # 进度、复制、打印 ├─ lib/documents.ts # 文档索引与 DocumentItem ├─ loading.tsx # 路由加载态 ├─ error.tsx # 可恢复错误态 ├─ not-found.tsx # 404 └─ chatgpt-auth.ts # 可选身份能力 public/docs/ # 独立富 HTML 文档 worker/index.ts # Worker fetch 入口 db/ # 可选 D1 适配(当前空 schema) scripts/ # 文档导入 / 转换 tests/ # 构建产物与文档结构测试
身份和数据库辅助代码属于预留骨架;当前页面没有调用 requireChatGPTUser() 或 getDb(),
托管声明中的 D1 / R2 也均为 null。产品说明不得把它们表述为现有功能。
关键请求与数据流
不同 URL 进入不同的交付路径。理解这些路径,是定位 404、样式不一致、图片失败和导航错误的基础。
A. 首页发现流程
文档卡片与链接随服务端 HTML 一起到达;HomeClient 水合后增强搜索、筛选、排序和对话框。 无 JavaScript 时仍可浏览初始列表。搜索复杂度随文档数线性增长,当前数据量很小,不需要网络搜索服务。
B. 独立富文档流程
该路径不需要 React 水合;每篇文档对自身结构和脚本安全负责。
C. 通用阅读器流程
未知 slug 调用 notFound;generateStaticParams 当前为所有索引条目生成参数。
D. 图片优化流程
路由责任矩阵
/documents.ts + React/docs/name.htmlpublic/docs/*.html/docs/:slug/_vinext/image
对带 href 的富文档,首页访问 /docs/slug.html,但
generateStaticParams() 仍为 /docs/slug 生成通用页面;下一篇导航也固定使用无后缀路由。
这可能造成内容重复、分享链接不一致和读者跳转到示例模板。
内容模型与发布流程
页集目前采用“文件即内容、TypeScript 数组即目录”的模型。它简单、透明、适合版本控制,但依赖强约定和发布前检查。
DocumentItem 领域模型
| 字段 | 必填 | 用途 | 约束 / 注意事项 |
|---|---|---|---|
slug |
是 | 稳定标识与通用路由参数 | 应唯一、URL 安全;当前没有自动唯一性校验 |
href |
否 | 覆盖默认动态路由,直达独立 HTML | 文件必须真实存在;建议只允许站内绝对路径 |
title |
是 | 卡片、元数据和搜索 | 应与文档 h1 / title 保持一致 |
category |
是 | 分类筛选与主题展示 | 当前是自由文本,易产生同义分类 |
description |
是 | 卡片摘要、页面描述与搜索 | 应简明且不包含不受信任 HTML |
updated |
是 | 机器排序 | 使用 ISO YYYY-MM-DD |
updatedLabel |
是 | 面向读者显示 | 与 updated 重复,存在漂移风险 |
readingTime |
是 | 阅读预期 | 当前为展示字符串,未自动计算 |
version |
是 | 内容版本 | 当前无版本比较或历史归档机制 |
当前发布流水线
内容入口策略
- 长篇研究、架构或工程治理文档
- 需要复杂表格、专属图示、主题或页面内搜索
- 希望脱离应用仍可单文件阅读和打印
- 愿意承担独立样式与脚本的维护成本
- 结构固定、内容较短、无需专属交互
- 优先追求全站一致和低维护成本
- 希望统一继承应用元数据、导航和错误状态
- 正文未来可由结构化来源生成
可将 DocumentItem 迁移为 JSON / TypeScript 清单并增加 schema 校验,构建时验证 slug 唯一、 href 文件存在、title 一致、日期合法、站内链接有效;不必立即引入 CMS。
运行时与部署拓扑
本地和生产使用同一套 vinext / Vite 构建链。生产入口是 Worker,静态文档与应用产物一起被托管版本交付。
构建与发布拓扑
构建配置事实
| 配置点 | 当前值 / 行为 | 意义 |
|---|---|---|
| 应用插件 | vinext() + sites() + cloudflare() |
同时提供 Next 编程模型、Sites 产物约定和边缘环境 |
| Worker main | ./worker/index.ts |
显式接管 fetch 与图片优化 |
| 兼容标志 | nodejs_compat |
允许使用平台提供的 Node 兼容能力 |
| Vite 环境 | rsc,子环境 ssr |
支持 React Server Components 和服务端渲染 |
| 开发状态 | Wrangler / Miniflare 状态写入项目内 .wrangler |
避免污染用户全局目录,便于隔离与清理 |
| 托管资源 | project_id 已配置;d1: null、r2: null |
当前部署不要求数据库或对象存储 |
| 测试产物 | dist/server/index.js |
测试直接导入构建后的 Worker 并发起 Request |
| 生成配置 | Worker ES module;兼容日期 2026-05-15;observability 开启 | 值来自当前构建产物,仓库没有独立的 wrangler.toml / json 源配置 |
| Sites 版本材料 | 构建插件把 hosting 声明与 drizzle/ 复制到 dist/.openai |
托管版本可同时携带逻辑资源声明与迁移元数据 |
环境与绑定
| 绑定 / 环境 | 本地 | 生产 | 失败影响 |
|---|---|---|---|
| ASSETS | Cloudflare 本地环境提供 | 托管平台注入 | 静态文档和源图片无法读取 |
| IMAGES | 平台模拟 / 注入 | 需由 Sites / 平台注入;当前生成 Wrangler 配置未显式列出 | /_vinext/image 路径失败;普通 HTML 仍可能可读 |
| DB | 未声明 | 未声明 | 当前无影响;调用 getDb 时会显式报错 |
| R2 | 未声明 | 未声明 | 当前无影响 |
| 身份请求头 | 通常不存在 | 由访问控制层决定 | 当前页面未要求身份,不影响公开阅读路径 |
内容与应用同版本交付,最安全的回滚是重新部署上一个已验证版本,而不是在线修改生产文件。 发布记录应保留版本、提交、验证结果和回滚理由。
依赖升级可能改变兼容日期、绑定或资产行为。CI 应保存或比对关键生成配置,并在真实托管环境冒烟验证 ASSETS、IMAGES 和主要路由,而不是只依赖本地构建成功。
数据、状态与存储策略
当前系统没有业务数据库。持久事实来自 Git 版本库中的索引和 HTML,运行时状态主要存在于单次浏览器会话。
版本库
- DocumentItem 元数据
- 独立 HTML 与站内资源
- 应用代码、脚本、测试与配置
浏览器内存
- 搜索词、分类、排序方式
- 移动菜单和对话框状态
- 阅读进度与提示消息
D1 / R2
- D1:结构化记录与关系查询
- R2:大文件与媒体对象
- 当前均未绑定、未承载业务数据
状态分类与生命周期
| 数据 | 来源 | 生命周期 | 敏感性 | 恢复方式 |
|---|---|---|---|---|
| 文档正文 | 版本库文件 | 随版本长期保存 | 取决于内容;默认应视为可发布资料 | Git 历史 / 上一部署版本 |
| 文档元数据 | documents.ts |
随版本长期保存 | 低 | Git 历史 |
| 搜索与筛选 | 浏览器操作 | 页面会话 | 低;不上传 | 无需恢复 |
| 主题偏好 | 独立文档浏览器设置 | 可选 localStorage | 低 | 用户重置 |
| 托管配置 | .openai/hosting.json |
项目生命周期 | 项目标识,不应包含密钥 | 版本库 + 平台控制面 |
当前 db/schema.ts 为空、迁移 journal 没有条目,脚本只提供 generate 而没有 migrate /
rollback / backup。更重要的是,hosting 声明可填任意 D1 绑定名,而 getDb() 固定读取
env.DB;Worker 的手写 Env 又把 DB 声明为必填。启用前应统一绑定名、生成类型并补齐环境化迁移流程。
何时引入持久化
- 需要在线编辑、审批、收藏、评论或权限记录
- 文档元数据无法继续在构建期维护
- 需要服务端复杂筛选、审计与事务一致性
- 可以承担 schema 迁移、备份和数据访问治理
- 大型图片、附件或视频不适合进入 Git
- 需要独立生命周期、版本或访问控制
- 静态资源总量和构建时间成为明确瓶颈
- 已定义对象命名、清理、备份和引用完整性策略
ADR 至少回答数据所有者、分类、保留期、迁移、备份、恢复、删除、访问审计和故障降级; 若这些问题没有答案,新增绑定只会扩大运维面。
安全与隐私架构
当前攻击面小,但“可执行的独立 HTML 与主站同源”是最重要的信任边界。只读并不等于没有脚本、供应链或内容泄露风险。
主要信任边界
| 边界 | 进入的数据 | 当前控制 | 剩余风险 |
|---|---|---|---|
| 互联网 → Worker | URL、查询参数、请求头 | 平台隔离、路由分发、图片宽度白名单 | 缺少应用级速率、异常与安全头证据;元数据生成依赖转发 Host |
| 内容贡献 → 版本库 | HTML、CSS、JavaScript、图片、链接 | 代码评审、CI 结构测试、禁止当前文档远程图片 | 同源脚本可访问站点能力;无统一内容安全扫描 |
| 构建依赖 → 产物 | npm 包、Actions、插件 | 锁文件、固定版本、CI 只读 contents 权限 | 没有依赖漏洞 / 许可证 / SBOM 门禁 |
| 托管平台 → 应用 | ASSETS、IMAGES、身份头、未来 DB/R2 | 逻辑资源声明与运行密钥分离 | 绑定契约、身份头来源和失败策略需持续验证 |
已有控制
- 核心产品为只读浏览,没有表单数据持久化。
.env、令牌和 IDE 本地配置被明确排除在版本控制之外。- 身份回跳只接受同源相对路径,并排除登录、登出和回调保留路径。
- 全名头只在声明 percent-encoded UTF-8 时解码,异常解码安全返回 null。
- 现有文档测试禁止通过 HTTP(S) 加载外部图片。
- 未见应用代码显式配置 CSP、Permissions-Policy 等响应头。
- 独立文档允许内联脚本,必须假设内容作者具有同源代码执行能力。
- PowerShell 导入依赖正则替换,可能把未预期标记带入最终文档。
- 身份辅助尚未接入页面,不能视作路由保护。
- CI 未包含依赖审计、秘密扫描或静态安全分析。
威胁与缓解
| 威胁 | 可能路径 | 影响 | 建议缓解 |
|---|---|---|---|
| 恶意文档脚本 | 将未审查 HTML 放入 public/docs |
同源脚本执行、钓鱼、未来身份信息滥用 | 仅接收可信内容;脚本白名单 / 静态扫描;不可信内容隔离到无凭证独立域 |
| XSS / 标记注入 | 未来从外部系统读取标题或正文并使用 HTML 注入 | 会话与内容完整性受损 | 继续使用 React 文本转义;外部 HTML 必须清洗;禁止危险 innerHTML |
| 开放重定向 | 伪造登录 return_to | 用户被引导到钓鱼站点 | 保留现有 safeRelativeReturnPath 测试,并覆盖编码与协议边界 |
| 供应链污染 | 依赖、Action 或构建插件被替换 | 构建产物被植入代码 | 依赖审计、更新策略、最小 CI 权限、来源固定与产物复核 |
| Host / 元数据污染 | 不可信代理头进入 metadataBase | 分享 URL 错误、缓存或 SEO 污染 | 仅信任受控代理覆写的头;对协议和主机做允许列表或使用固定站点基址 |
| 敏感内容误发布 | 把内部资料作为静态文档提交 | 不可逆的信息暴露与缓存扩散 | 内容分级、发布审批、敏感词 / 秘密扫描、明确站点访问策略 |
如果未来允许用户上传或在线生成 HTML,应采用严格清洗、沙箱 iframe 或独立无凭证域名; 不能沿用当前“将文件直接放入 public/docs”的可信作者模型。
性能、可靠性与可观测性
静态优先天然降低了故障面,但当前缺少运行数据闭环。优化应先测量,再针对真实瓶颈,而不是先增加缓存或搜索基础设施。
短读路径
直达文档无需数据库或外部 API;首页元数据随应用输出;边缘执行减少源站往返。
全量元数据水合
首页把全部 DocumentItem 传给客户端并做 O(n) 检索;小规模合适,大规模需重新评估。
平台开关不等于运行闭环
生成配置已开启 observability,但仓库未定义采样、结构化日志、Web Vitals、404、告警与版本关联策略。
容量演进触发点
建议阈值 以下不是平台上限,而是用于触发重新测量和设计评审的经验门槛。
| 信号 | 继续当前方案 | 触发评审 | 可能演进 |
|---|---|---|---|
| 文档条目数 | < 500 | 接近 500 或首页载荷明显增长 | 生成轻量搜索索引、分区加载或服务端查询 |
| 静态媒体 | 可接受的 Git / 构建体积 | 克隆、构建或部署时间明显恶化 | R2 + 资源清单 + 生命周期策略 |
| 更新频率 | 随代码版本发布 | 内容团队需要独立高频发布 | 内容清单自动生成或受控 CMS 流程 |
| 个性化需求 | 匿名统一内容 | 收藏、权限、审批或审计成为刚需 | 身份边界 + D1 + 权限模型 |
已知性能细节
HomeClient的同步文档列表随服务端 HTML 输出,浏览器水合只负责交互增强;路由级loading.tsx负责真实等待状态。- 客户端搜索把标题、摘要与分类拼接后做小写包含匹配,结果即时但不支持分词、拼音、权重与高亮。
- Worker 图片优化限制宽度集合,避免任意尺寸放大处理面;SVG 默认绕过优化端点。
- 独立文档将 CSS / JS 内联,减少请求数并增强可移植性,但会增加单个 HTML 体积并降低跨文档缓存复用。
- 全局样式提供响应式、打印与 reduced-motion;发布前仍应以真实内容检查超宽表格和长代码。
失败模式与降级
| 失败模式 | 当前表现 | 影响范围 | 建议检测 / 处置 |
|---|---|---|---|
| 未知路由 | 展示中文 404 页面 | 单个请求 | 监控 404 比例;校验内部链接 |
| 应用渲染异常 | 展示可重试错误页 | 受影响路由 | 记录异常指纹、发布版本和请求路径 |
| HTML 文件缺失 | 静态 404;首页仍可能展示卡片 | 单篇富文档 | CI 校验 href 文件存在 |
| IMAGES 不可用 | 优化端点失败 | 经优化图片 | 告警绑定错误;关键图可提供原图回退 |
| 客户端 JavaScript 禁用 | 首页初始内容可见,但筛选和工具不可用 | 交互增强 | 保持 SSR 内容与原生链接;避免核心正文依赖 JS |
最小可观测性方案
- 构建成功率与耗时
- 部署版本、提交和发布时间
- 首页、三篇富文档的合成探测
- 回滚次数与原因
- Worker 5xx、静态 404、图片优化失败率
- 首页与文档的 LCP / CLS / INP
- 热门文档与无结果搜索(需隐私审查)
- 错误版本关联与告警降噪
测试、CI 与质量门禁
当前测试重心是“构建产物能否服务首页”和“富文档结构是否完整”。它覆盖了关键冒烟路径,但尚未形成完整的行为、可访问性与安全回归网。
当前质量链
| 验证层 | 当前覆盖 | 证据 | 主要缺口 |
|---|---|---|---|
| 静态规范 | ESLint、TypeScript strict | npm run lint、npm run typecheck |
独立 HTML 内联脚本不在 TypeScript / ESLint 覆盖内 |
| 构建冒烟 | 导入 dist/server/index.js,请求首页并断言 200 / HTML |
tests/rendered-html.test.mjs |
没有真实平台绑定与生产 URL 探测 |
| 首页契约 | 语言、标题、主要文案、文档链接、跳转链接、无预览残留 | 正则断言服务端 HTML | 搜索、筛选、排序、对话框和移动菜单行为未测试 |
| 富文档结构 | 章节数量、锚点、进度、复制、响应式、表格、远程图片 | 读取 HTML 并做结构正则 | 链接有效性、标题一致、重复 ID、脚本运行与视觉回归 |
| 导入器契约 | 转换脚本与已发布布局关键规则一致 | 脚本文本断言 | 没有以样例输入执行端到端转换并比较语义输出 |
| CI | main 的 push / pull_request,Ubuntu,Node 22.13.0,15 分钟上限 | .github/workflows/ci.yml |
无依赖安全、可访问性、断链与部署后检查 |
建议的增量测试金字塔
纯逻辑单测
搜索、排序、slug / href 解析、安全回跳、日期与分类规范化。
内容清单检查
唯一 slug、文件存在、内部链接、重复 ID、title / h1 / 索引一致。
Worker 路由
首页、通用文档、静态 HTML、404、图片优化和绑定缺失行为。
浏览器与探测
键盘、窄屏、打印、Web Vitals、生产链接和部署后冒烟。
先运行毫秒级逻辑与清单校验,再进行类型和构建,最后执行浏览器 / 部署探测。 失败信息应直接指向文档 slug、文件、锚点或路由,降低内容维护者的排查成本。
架构决策记录摘要
下表把代码中已经形成的关键选择显式化。后续如推翻任一选择,应新建 ADR,而不是静默改变隐含前提。
| ID | 决策 | 状态 | 理由 | 代价 / 退出条件 |
|---|---|---|---|---|
| ADR-001 | 静态内容优先 | Accepted | 读多写少、内容可版本化、运行依赖少 | 在线协作与个性化成为核心需求时重审 |
| ADR-002 | 富文档与通用阅读器双路径 | Accepted with debt | 在表达自由度与一致维护之间折中 | 必须建立唯一 URL、导航和准入标准 |
| ADR-003 | 浏览器端搜索与筛选 | Accepted | 数据量小、零网络请求、交互即时 | 载荷或搜索质量不满足目标时重审 |
| ADR-004 | vinext / Vite 构建到 Cloudflare Worker | Accepted | 保留 Next 编程模型并获得边缘交付 | 升级需验证兼容面与产物契约 |
| ADR-005 | TypeScript 数组作为文档清单 | Temporary | 实现最简单、类型明确、当前规模可控 | 手工漂移增多时迁移到可生成、可校验清单 |
| ADR-006 | D1 / R2 默认关闭 | Deferred | 当前没有持久化业务需求 | 触发条件见数据与存储章节 |
| ADR-007 | 独立文档尽量无外部依赖 | Accepted | 可移植、可离线阅读、避免第三方失效 | 以 HTML 体积和跨页复用为代价 |
| ADR-008 | 身份能力按需接入 | Deferred | 当前核心阅读路径不需要应用内身份 | 出现私有分区或用户数据时先完成权限模型 |
ADR 模板
# ADR-XXX:决策标题 - 状态:Proposed / Accepted / Superseded / Rejected - 日期、责任人、关联需求 - 上下文:什么变化迫使我们决策? - 决策:选择什么?明确不选择什么? - 备选:至少一个可行替代方案 - 后果:收益、成本、风险与迁移影响 - 验证:怎样证明决策有效? - 退出条件:何时必须重新评审? - 迁移与回滚:失败时如何安全退出?
风险台账与技术债
风险按“影响 × 发生可能性 × 可检测性”综合排序。优先级用于安排治理顺序,不代表每项都需要立即重构。
| ID | 风险 | 影响 | 可能性 | 优先级 | 建议动作 | 责任角色 |
|---|---|---|---|---|---|---|
| R-01 | 不可信独立 HTML 在主站同源执行脚本 | 高 | 中 | P0 | 限定可信作者;内容脚本检查;未来上传隔离域 / sandbox | 安全 + 内容平台 |
| R-02 | /docs/slug 与 /docs/slug.html 内容和导航不一致 |
中 | 高 | P1 | 定义 canonical URL;所有导航统一使用 resolveDocumentHref | 应用维护者 |
| R-03 | HTML 文件与手工索引漂移 | 中 | 高 | P1 | schema + 文件存在 + slug 唯一 + title 一致性测试 | 内容平台 |
| R-04 | 仓库未显式配置统一安全响应头 | 高 | 中 | P1 | 核对托管默认值;设计兼容内联文档的 CSP 迁移方案 | 运行时 + 安全 |
| R-05 | 测试以正则结构断言为主,真实交互和无障碍回归不足 | 中 | 中 | P1 | 增加逻辑单测、DOM 契约、键盘与窄屏冒烟 | 质量负责人 |
| R-06 | 缺少应用级可观测性,发布后退化发现慢 | 中 | 中 | P1 | 建立合成探测、错误率、404、Web Vitals 和版本关联 | 运行时维护者 |
| R-07 | 正则式导入脚本对源 HTML 结构敏感 | 中 | 中 | P2 | 固定样例输入;执行端到端转换;逐步采用 DOM 解析 | 内容工具维护者 |
| R-08 | 数据库、身份和示例代码增加认知噪声 | 低 | 高 | P2 | 明确标注可选;不使用则移动到示例或删除死代码 | 架构维护者 |
| R-09 | updated 与 updatedLabel 双字段可能不一致 | 低 | 中 | P2 | 仅保存 ISO 日期,显示层统一格式化 | 内容平台 |
| R-10 | 根元数据直接使用转发协议与 Host 构造站点基址 | 中 | 低至中 | P2 | 确认可信代理覆盖;增加协议 / 主机允许列表或固定生产基址 | 运行时 + 安全 |
| R-11 | D1 配置可使用任意绑定名,但数据适配固定读取 env.DB | 高(启用后) | 中 | P1 | 启用前统一绑定契约、生成 Env 类型,并增加真实 D1 集成测试 | 数据 + 运行时 |
| R-12 | 图片优化依赖 IMAGES,但生成 Wrangler 配置未显式列出绑定 | 中 | 中 | P1 | 确认 Sites 注入契约;部署后冒烟;缺失时提供结构化错误或原图降级 | 运行时 + 平台 |
| R-13 | 生产 Wrangler 配置完全由构建生成,升级可能静默改变运行契约 | 中 | 中 | P2 | 在 CI 快照和比对关键配置、兼容日期、资产目录与可观测性开关 | 运行时维护者 |
推荐处理顺序
- 先守边界:明确独立 HTML 的可信作者模型与未来不可信内容隔离方案。
- 再消除歧义:统一文档 URL 解析、下一篇导航和 canonical 元数据。
- 把约定自动化:用清单 schema 和文件 / 链接检查阻断索引漂移。
- 补发布闭环:增加最小合成探测、错误指标和部署版本关联。
- 最后降维护成本:重构导入器、删除死骨架、统一重复元数据。
演进路线图
路线遵循“治理先于扩容、证据先于基础设施”。每一阶段都应能独立交付价值,也能在需求没有增长时安全停留。
让现状可靠
- 统一 resolveDocumentHref 与下一篇导航
- 确定富文档 canonical URL
- 增加 slug、href、title、内部链接检查
- 保持同步列表服务端直出,避免重新引入假加载
- 核对并记录生产安全响应头
- 确认 ASSETS / IMAGES 的真实托管绑定契约
- 建立首页和关键文档合成探测
把约定变成平台
- 结构化内容清单与 schema 校验
- 从清单生成首页索引与静态参数
- 导入器端到端样例测试
- 键盘、窄屏、断链与可访问性门禁
- 发布版本、错误率、404 和 Web Vitals 看板
- 在 CI 比对生成的 Worker / Wrangler 关键配置
- 依赖审计和敏感内容扫描
只为真实需求增加状态
- 文档量增长后生成轻量全文索引
- 大型媒体增长后引入 R2
- 收藏、审批或在线编辑出现后引入 D1
- 启用 D1 前统一绑定名、类型、迁移与备份流程
- 私有分区出现后接入身份与授权
- 不可信内容使用隔离域或沙箱
- 形成正式 ADR 库与季度架构复盘
目标架构原则
一个内容事实源
文件、元数据、路由和导航由同一清单派生,杜绝多处手工重复。
一条 canonical 路径
每篇内容只有一个分享、索引和 SEO 主 URL;兼容入口只做明确重定向。
可信内容与不可信内容隔离
作者信任模型进入架构,不把可执行 HTML 当作普通数据文件处理。
静态是默认,状态是例外
只有明确业务场景和运维责任时才引入数据库、对象存储或身份化路径。
发布即验证
构建、内容契约、安全、可访问性和部署探测共同定义“可以发布”。
指标驱动扩展
以真实文档量、体积、性能和协作需求触发架构升级,而非预设复杂度。
如果当前静态方案持续满足质量目标,可以停留在任一阶段。架构成熟度不由服务数量衡量, 而由边界是否清楚、失败是否可见、变更是否可验证来衡量。
运维手册、责任边界与附录
本节把架构落到日常动作:如何验证、如何发布、出现问题先查哪里,以及哪些文件是各类能力的事实来源。
常用操作
| 场景 | 操作 | 通过标准 |
|---|---|---|
| 首次安装 | npm ci |
依赖严格按锁文件安装,无未解释的版本改写 |
| 本地开发 | npm run dev |
首页、通用路由和独立 HTML 可访问 |
| 完整检查 | npm run check |
Lint、类型、构建与页面测试全部通过 |
| 单独构建 | npm run build |
生成 Worker 兼容生产产物,无关键警告 |
| 新增文档 | 放入 public/docs,更新索引和测试 |
首页可发现、链接可达、移动端 / 打印可读 |
| 回滚 | 选择上一个已验证站点版本重新部署 | 关键 URL 探测恢复,错误率回落,并记录原因 |
故障排查速查
首页正常,但某篇独立 HTML 返回 404
- 核对
documents.ts的 href 与public/docs文件名,包括大小写和后缀。 - 确认文件被纳入构建 / 部署版本,而不是只存在于本地。
- 检查是否把无后缀通用路由当作富文档 canonical URL。
- 修复后运行完整检查并重新部署,不要在线补文件。
首页出现文档,但点击后进入示例模板
- 确认该条目是否需要
href: "/docs/name.html"。 - 检查导航是否硬编码为
/docs/${slug}。 - 统一通过文档 URL 解析函数生成首页和“下一篇”链接。
图片优化失败,但原始文档文本仍可访问
- 确认请求是否进入
/_vinext/image。 - 检查 ASSETS 能否找到原图以及 IMAGES 绑定是否存在。
- 确认宽度属于默认设备尺寸或图片尺寸白名单。
- 关键内容应提供原图或文本替代,不让优化服务成为正文单点。
构建通过,但发布页面内容不符合预期
- 核对部署版本对应的提交和构建产物。
- 检查独立 HTML 是否由导入脚本重新生成后未提交。
- 确认缓存和 canonical URL,没有访问到另一条阅读路径。
- 若无法快速定位,回滚到上一个已验证版本后再离线分析。
发布完成定义
- 标题、摘要、分类、版本与更新时间准确
- 正文来源和引用已由内容责任人确认
- 内部链接、锚点、图片和表格可用
- 不存在敏感信息或未经授权资料
- 唯一 URL 与首页、下一篇导航一致
- 键盘、窄屏、打印和减弱动态效果可用
- 完整检查通过并记录构建证据
- 部署后关键 URL 探测成功,回滚版本可用
责任分工
| 事项 | 主责 | 协作 | 最终确认 |
|---|---|---|---|
| 内容事实与授权 | 内容作者 / 业务负责人 | 编辑、法务或安全 | 内容所有者 |
| 索引、路由与阅读体验 | 前端 / 应用维护者 | 内容维护者、设计 | 产品负责人 |
| Worker、绑定与部署 | 运行时维护者 | 平台、安全 | 技术负责人 |
| 质量门禁与发布证据 | 变更作者 | 质量负责人、评审者 | 合并审批者 |
| 架构决策与风险台账 | 架构维护者 | 产品、研发、运维、安全 | 技术负责人 |
关键文件地图
| 问题 | 首先查看 | 其次查看 |
|---|---|---|
| 首页有哪些文档? | app/lib/documents.ts |
app/components/HomeClient.tsx |
| 文档 URL 为什么这样生成? | documents.ts 的 href / slug |
app/docs/[slug]/page.tsx |
| 请求如何进入边缘应用? | worker/index.ts |
vite.config.ts |
| 当前是否使用数据库或对象存储? | .openai/hosting.json |
db/index.ts、db/schema.ts |
| 发布前检查什么? | package.json scripts |
tests/rendered-html.test.mjs、CI workflow |
| 富文档如何生成? | scripts/import-*.ps1 |
public/docs/*.html |
| 品牌与页面语言是什么? | brand-spec.md |
app/globals.css |
术语表
Worker 环境中的静态资源读取绑定。
平台图像变换能力,用于缩放、格式与质量输出。
Cloudflare 边缘 SQLite 数据库;当前未启用。
对象存储;适合大媒体与附件,当前未启用。
React Server Components,在服务端生成组件结果。
一份内容被搜索、分享与引用时采用的唯一主地址。
每次启用新绑定、改变 canonical 路由、引入身份化页面、修改内容信任模型或替换构建运行时, 都应更新对应章节、风险台账和 ADR;至少每季度复核一次“当前事实”标签。