FROM THE NOTEBOOK

一次 undefined is not iterable 背后:三档页面路由如何失败又如何被固定

从主题 accent 缺失引发的真实崩溃出发,检查 Content Schema、构建期路由注入、full/minimal/none 隔离和容错边界。

穿孔卡片与彩线构成的内容路由器,其中一条珊瑚色线路留有修复接缝

一次本地预览中,首页没有出现,Astro 开发错误页停在 lib/visual.ts:20

TypeError: undefined is not iterable
const [secondary, tertiary] = ACCENT_RELATIONS[accent]

它表面上只是一个很小的空值错误,却刚好暴露了站点架构的一条关键依赖链:内容 frontmatter 定义主题 accent,列表组件将其转换为 CSS 变量,路由整合器再把文章送入不同的页面外壳。链上任何一段把“可选”错当成“必定存在”,都可以让整个聚合页无法渲染。

chrome 不是主题,也不是视觉等级

站点将页面外壳分为 full / minimal / nonefull 有站点顶栏、环境场、页脚和阅读控件;minimal 保留基础阅读表面与返回桥;none 只使用文档骨架,页面内部完全由内容自己决定。

主题则是另一个维度。一篇文章可以属于“工程实践”,却使用 none 的独立交互页;一个主题也可以自己使用 none,并拥有与站点不同的整页设计。把 chrome 用来表示“文章类型”会让这两个维度重新纠缠。

路由不靠六份手工列表维护

Astro 构建时,content-routes.mjs 读取文章与主题 frontmatter,为每个内容 ID 注入唯一路由,并将其指向 ${collection}-${chrome}.astro 的对应入口。新增 none 主题不需要再去中央 switch 里注册;改动 chrome 也不改变永久 URL。

六个入口看似有重复,却交换来一个可静态检查的隔离性质:none 路由不能间接携带 FullShell、分析、评论、粒子或阅读控件。测试会直接搜索路由源文件和构建产物,而不只相信组件命名。

那次 accent 崩溃为什么绕过了类型

内容 schema 给 accent 提供默认值,但在开发过程的一个中间状态中,组件拿到了尚未通过完整 collection 解析的数据。accentStyle 直接对 ACCENT_RELATIONS[accent] 解构,等于把“查表必然命中”当成运行时不变量。

修复不只是在调用处填一个 'aqua'。视觉帮助函数自身现在对非法或缺失值回退到已知 accent,而 schema 仍在内容边界拒绝不支持的字符串。前者保证预览和过渡状态不白屏,后者保证错误不被永久吞掉。

返回按钮也是路由合同的一部分

一开始,full 页面依赖顶栏 logo 回到首页,minimal 与 none 则可由 frontmatter 决定是否显示返回桥。这对直接打开链接的读者尚可接受,对从主题、搜索或关联文章进入的人却不够:“回首页”不等于“回到我刚才的上下文”。

现在所有非首页页面都带有返回桥。若 referrer 是同源且路径不同,它调用浏览器历史返回;若用户是从外部链接直达,它就使用真实 href="/" 作为可无 JavaScript 降级的出口。按钮放在左下角,避免覆盖拥有独立顶栏的 none 页面。

一个小错误最后变成了五道门禁

现在的回归要求同时覆盖:全部文章引用存在的主题;两种 collection 都保留三档 chrome 样本;none 产物不携带全站资源;首页列表能对所有 accent 生成样式;每个非首页有可用返回路径。

事故的根因不是解构语法,而是可选值跨层失真

触发崩溃的代码直接解构 ACCENT_RELATIONS[accent]。类型层曾把 accent 当作有限枚举,但历史内容、构建期读取与局部组件并没有共享同一条归一化边界;一旦值缺失,视觉工具就收到 undefined。修复不能只写一个 || ['aqua','gold'],还要明确哪一层负责把未知输入归一为安全的语义值。

当前方案在 Content Schema 限制作者输入,在 accentStyle 保留运行时回退,并用单元测试遍历全部合法 accent 与未知值。Schema 保护正式内容,运行时回退保护开发态、旧缓存和将来的局部调用;两者职责不同,不能因为已有类型就删除容错。

本地图片错误揭示了另一条边界

Astro 的 <Image> 要求本地图片经过静态导入或 content collection 的 image() schema 解析,不能把 /cover.png 字符串冒充导入元数据。之前的错误页正是把 frontmatter 字符串直接交给图片管线。现在封面字段由 schema 解析为 ImageMetadata,组件只在拿到合法导入时使用优化图片;缺失封面则进入确定性默认图。

这条约束同时进入封面审计:图片存在、尺寸与比例合格、alt 非空,构建才继续。开发态的容错可以避免一个坏条目拖垮整个首页,但正式验收不能把降级当作资产已经正确。

返回按钮必须恢复上下文

全站返回桥优先使用同源 document.referrer 与浏览历史;没有可靠来源时才回到文章所属主题或首页。它不能把所有“返回”都写成 /,因为从 ArcVellum 侧栏进入文档、从搜索进入文章、从笔记反向链接进入另一条笔记时,用户期望回到刚才的索引。

浏览器级测试因此不仅寻找一个包含“返回”的元素,还要实际从列表点击进入、触发返回并检查 URL。架构门禁验证每个页面有逃生路径,交互测试验证这条路径是否真的保留阅读上下文。

复盘的价值不是证明 TypeScript 也会有空值,而是承认:“内容是数据”只有在运行时容错、构建期验证和页面导航三者一起存在时,才不是一句架构口号。

本文采用 CC BY-NC-SA 4.0 许可;转载时请保留作者与原文链接。