知行札记
本站阅读体验

实现与验收:静态文档站的引用阅读器

比较静态预览页、客户端 MDX 和摘要路线,核对发布过滤、链接、ID、焦点与历史边界,给出实施顺序和可验证条件。

核查于

推荐路线与确认边界

整站外壳按新的视觉定稿设计,并接入统一引用阅读器:桌面使用非模态面板,窄屏使用模态覆盖阅读。全文内容优先验证“共享服务端正文组件+静态轻量预览路由+同源 iframe”。它能延续当前 MDX 编译、相对链接解析和发布过滤,并隔离两篇文章的标题、脚注及布局 ID。

视觉方向先通过完整设计稿确定,技术路线随后做最小验证。这是代码与资料支持的第一候选。本次未构建预览原型或运行浏览器,焦点、资源成本和跨文档交互仍需验证。若这些边界在代表内容中表现不合适,再采用已经存在的客户端 MDX 按需加载能力,并补齐公开清单与正文隔离。

成熟能力与本站需要组装的部分

能力已核查的可用基础本站新增职责
亮暗模式RootProvider、next-themes风格与阅读偏好接线
布局DocsLayout / DocsPage 的 slots 与选项面板打开后的网格、TOC 与窄屏分配
对话区域Base UI Dialog、Popover持续阅读、加载状态和目标切换
编译正文page.data.body、MDX 注册表复用正文渲染,去除多余整页导航外壳
发布范围过滤后的 source所有预览入口、路由和清单读取同一集合
相对链接服务端 createRelativeLink 和 source.resolveHref预览目标与正式目标的区分
浏览器 MDXFumadocs MDX Browser Entry若采用,限定公开 import 集合并隔离 DOM

本次检查官方组件与布局文档、本地安装包,未发现包含“获取另一篇正文、管理并排阅读与返回状态”的完整成品。Fumadocs 和 Base UI 提供构建所需的基础能力,引用阅读器由本站组装。Docs Layout、Base UI Dialog

桌面可采用 Dialog modal={false},并使用 disablePointerDismissal,防止点击原文或把焦点移出面板后关闭。窄屏使用模态焦点管理。已有 src/components/ui/dialog.tsx 的 DialogContent 固定组装遮罩和居中 Popup,不能仅传 modal={false} 就形成适合并排阅读的外壳;应使用相同 primitive 组装专用 ReaderPanel。Dialog API

静态导出约束

next.config.ts 采用 output: 'export'。内容页面通过 generateStaticParams 构建,source 在生产输入阶段过滤草稿。新增正文预览仍可在构建期生成 HTML、CSS 与 JS,不要求新增在线后端。

Next.js 官方 Static Exports 明确列出 Intercepting Routes 为不支持功能。常见用拦截路由打开 modal 的示例不能直接用于当前部署方式。本次同时核对本地 Next 文档中的对应限制。Static Exports

全文渲染候选比较

路线可以提供什么主要实现代价结论
静态预览路由+同源 iframe服务端 MDX、当前链接解析、真实交互组件、独立 document焦点、主题、内部导航、历史与弹层范围第一候选,先做代表内容验证
客户端 MDX 按需 import真实组件、同一 document 与主题上下文公开 import 集合、服务端链接迁移、ID 与观察器隔离第二候选,有成熟 loader 但接线工作明显
构建期摘要或章节摘录简介、脚注、局部文本裁剪完整语义、脚注与 MDX 组件的规则用于轻量预览,不能独立满足完整阅读
抓取正式页 HTML 后插入静态标记React 交互、模块初始化、ID 和链接适配不作为本站全文方案
每页预先带全站正文打开时减少单次获取首屏载荷、重复内容和公开边界不采用

不同路线均使用原始正文派生,不增加人工维护的第二份预览文章。自动生成的 HTML、摘要或 loader 清单是产物,后续可随正文重新生成。

第一候选怎样工作

正文、预览路由与公开集合

从文档页抽出共用的服务端 ArticleContent,把 page.data.body、相对链接解析、Card 解析和 MDX 注册表保留在这一边界。正式页组装完整导航与元信息;预览路由只组装阅读必需的正文和 provider。

预览路由通过同一 source.getPages() 生成静态参数。生产只生成公开页面及公开祖先下的内容;开发按现有规则显示草稿。新增预览路径需要纳入保留路由名,避免与作者目录冲突。其 metadata 指向正式页 canonical、设置 noindex,并不参与正文导航、搜索和 sitemap。

面板按点击时创建 iframe,一次显示一个目标。面板外壳负责标题、“打开全文”、关闭和加载反馈,iframe 内负责正文,避免重复标题和整套工具。重用现有样式与必要 provider;主题初始化、代码复制、Tabs、图片和图示依赖不能省略后宣称完整能力保留。

两个文档之间的协议

iframe 有独立的 document 和浏览上下文。标题、脚注和组件 ID 得到隔离,但父页面 CSS 不会自然控制子文档;焦点、事件和状态需要明确协同。iframe 本身也增加内存与运行开销。MDN iframe

建议定义有限的通信职责:子页报告已就绪、加载异常和未被内部弹层消费的关闭请求;父页传递当前偏好与目标;如果支持引用链,子页请求在同一面板继续查看。消息携带当前目标或请求标识,父页拒绝过时结果,并核对精确 origin、发送窗口和允许的站内目标。MDN postMessage

主题使用共享偏好来源。当前 next-themes 有同源持久化基础,新增风格、正文设置与动效开关还需初始化和同步;必须检查首次绘制及面板已打开时的改变。动效同时覆盖 CSS 与图示的 JavaScript,关闭收尾见动效设计。子页键盘事件不自动冒泡到父文档,Esc 传递应尊重内部图片或 Popover 的关闭层级。

iframe 的 load 不能独立证明目标加载成功,浏览器即使加载失败也可能触发该事件。使用子页 ready/error 握手、超时和正式链接回退;快速更换目标时,旧子页的消息不能把新目标标成成功。MDN iframe 的事件说明

正式地址、锚点与历史

同页标题和脚注在子文档中定位。首轮跨页链接指向正式地址,指定 target="_top" 或等效的父级正式导航,避免完整 Docs 页面被打开在预览 iframe 中;Ctrl/Cmd、中键和右键新标签继续沿用真实链接行为。后续若增加面板内预览入口,再向父页面请求替换引用,并同步提供局部返回。首次定位需要考虑字体、图示和图片加载造成的位移,并避免后续内容更新反复抢走读者位置。

外部链接首轮同样按真实 href 在主窗口正式打开,由读者选择浏览器新标签操作;标明新标签的操作再使用 _blank。目标窗口按链接用途逐项处理,不全局使用 <base target="_top">,以免把同页标题和脚注也带出预览。

默认 Heading 的复制锚点读取 window.location.href,在预览路径中会得到预览 URL。需要通过 Heading 适配将复制结果指回正式 canonical URL 加原始 hash。作者的相对页面链接继续以目标文章为解析上下文,不能根据主文或预览路由路径计算。

iframe 导航会影响页面会话历史。实现时需要明确首次创建、切换目标和同页定位的历史规则,尽量采用替换式更新,避免每次预览增加浏览器 Back 步骤。必须验收“切换数个引用—关闭面板—浏览器后退”的行为;仅保持顶层地址栏不变不足以证明历史不变。MDN iframe 浏览上下文说明

弹层与焦点的实际边界

图片放大与 Twoslash 等 Portal 默认位于子文档,只能覆盖引用窗口的范围。首轮应明示并检查该表现;如果引用图片必须占满顶层屏幕,则单独桥接图片查看器。不能把完整组件渲染等同于所有弹层自动覆盖主页面。

iframe 设置具体文章名的 title。父面板只用短标题描述阅读区域,长篇正文不整段放进 aria-describedby。桌面焦点能进出面板和主文,窄屏模态焦点必须包含子文档;关闭时返回有效触发点。自动化检查之外,还需要真实键盘与辅助技术核查。WAI-ARIA Dialog Modal Pattern

客户端全文为什么保留为候选

Fumadocs MDX 提供 Browser Entry,使用异步 import 和 createClientLoader 加载编译后的 MDX,具有预加载、内容读取和组件获取能力。本地 15.4.6 的生成文件与运行时已确认这些接口。Browser Entry

但当前 apps/site/.source/browser.ts 的 import 清单覆盖整个 content,包含草稿;src/lib/source.ts 的过滤发生在服务端 source。把 collections/browser 直接引入公开客户端后,不能仅凭点击前校验目标便证明草稿没有进入 JS chunks。需要生成只包含同一公开集合的 import 清单,或核查适用的官方编译过滤入口,并检查实际产物。

这属于新增路线的构建风险,本次没有发现当前站点已经通过客户端 loader 输出草稿的证据。后续清单必须由正文公开集合派生,不能人工列维护两套页面。

另一个边界是 createRelativeLink 为服务端接口。本地浏览器实现直接抛出只支持 Node 环境的错误。客户端全文需在构建期派生已解析 href 或映射,并覆盖 Markdown 链接、Card 与静态 MDX href;query 和 hash 继续保留。

同一 document 内的双正文还需处理:

  • 标题、脚注、作者 HTML id 以及对应 hash、ARIA 引用的一致隔离。
  • Fumadocs TOC 当前通过 document.getElementById 查标题,其观察与滚动逻辑不能直接当作两份正文的隔离方案。
  • 面板内锚点定位到指定滚动容器,避免滚动主页面。
  • 避免嵌入第二个完整 DocsPage 后重复固定布局 ID;使用独立 ReaderBody。
  • Mermaid、图片放大和类型弹层的上下文、事件和所在区域。

客户端方案能共享主题和减少跨文档通信。若原型证实其公开清单、编译链接和正文隔离能以更小维护成本解决,可以调整最终选择。当前保留两条候选是为了明确未验问题,交互层的目标与操作规则保持一致。

状态与改动位置

阅读器只保存无法直接推导的状态:当前预览目标及锚点、有效触发元素、加载请求结果,必要时保存面板局部阅读位置和引用历史。开关可由目标是否存在派生;宽屏与窄屏呈现由可用空间派生;页面标题和正式 URL 来自 source 派生的只读资料。

位置后续实现职责
文档布局挂载统一阅读器,分配右侧 TOC 与面板空间
文档页面共用正文组装,传递正式地址与最小预览资料
page-relations.tsx改为紧凑条目,标题链接和预览按钮相邻
MDX 链接适配在正文站内引用提供预览入口,保留真实 href
新增轻量预览路由静态参数、草稿过滤、canonical、noindex、ReaderBody
新增 ReaderPanel非模态/模态呈现、焦点、加载和目标替换
tokens.css / theme.css / prose.css阅读专属值、面板网格与风格变量
content-index.ts若需要引用出现位置,从已有 AST 派生;现有页级索引无需人工补字段

首轮可只用现有页级关系接入面板。正文锚点和引用位置增强再沿原链接与 AST 补充,保持内容作者继续维护一份正文。清单、预览路由与索引都是生成结果。

实施与验收清单

先建立一篇代表内容,包含相对链接、中文标题锚点、脚注、代码、公式、Mermaid、图片放大和 Tabs,同时设置草稿页面与草稿祖先作为排除样本。先验证静态编译、公开集合、正式链接和路由;随后进行已授权的浏览器验收。技术试验遵循本站共用验证环境与授权约定。

类别可验证条件
内容边界正式页和预览来自相同 source;公开路由、资源或客户端清单不意外包含被排除正文
链接解析上下文正确,hash/query 保留,正式打开和复制锚点指向原页面
完整 MDX代码复制、Tabs、公式、Mermaid、图片与类型提示在预览中按定义运行
异步快速切换、404、超时和晚到消息不会覆盖当前目标;正式打开始终可用
空间与滚动主文位置保留,两侧分别滚动,宽内容局部溢出;缩放与窄屏正确降级
焦点桌面可操作两边;窄屏模态可关闭并返回;Esc 尊重内部弹层;焦点不被遮挡
历史预览开关与切换不制造非预期 Back 步骤,正式导航后状态符合约定
主题风格、明暗、大字和系统变化在两边一致
动效开关保存并同步两边;中途关闭、连续操作后无残留动画、遮罩或滚动锁,焦点正常
性能打开前不载入整站正文;记录打开后的额外请求、JS、内存与首次可操作延迟
阅读效果同一核查任务比较正确性、往返、位置迷失和主观舒适度

性能需给出实际环境和数据,首轮不编造延迟或内存目标。若 iframe 的成本或双文档交互明显不适合,回到客户端候选做相同任务比较;不能仅凭抽象优缺点宣布性能更优。

本次只完成调研与草稿,没有进行以上原型、浏览器、屏幕阅读器或真实读者验证。正文格式检查证明文章符合本站技术接口,其结果不证明引用阅读功能已经实现或用户效果已经成立。

最后更新于

本页目录