欢迎访问 老鱼笔记今天是
无障碍与显示设置

静态站点有个隐藏的成本:内容错误往往不会报错,而是安静地消失。日期写错了、分类拼错了、字段名少了个字母,页面照样构建成功,只是那篇文章不再出现在任何列表里。等人发现时,通常已经过去很久。

问题的根源

早期的做法是“约定俗成”:所有文章都有 title、date、category,靠记忆保证格式统一。问题在于记忆不可靠,尤其在几个月后回来补一篇文章的时候。

解决思路只有一条:把约定变成可执行的校验。

用 Zod 定义内容结构

Astro 的内容集合允许为每类内容声明 schema,字段类型不对、必填项缺失都会在构建时报错:

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const posts = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
  schema: z.object({
    title: z.string().min(1),
    date: z.coerce.date(),
    category: z.string().refine(isRegisteredCategory, {
      message: 'category 必须是 site.config.ts 中已注册的分类 key',
    }),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

这段代码带来的实际收益是:写错分类时构建直接失败,并且错误信息会明确告诉你是哪一篇、哪个字段、可选值有哪些。

关于 z.coerce.date()

YAML 里的日期写法很容易不统一:

date: 2026-09-18            # 正常
date: "2026-09-18"          # 也能用
date: 2026/09/18            # 会解析失败

用 z.coerce.date() 把字符串或日期对象统一转成 Date,后续排序、格式化就都不需要再做兼容处理。

校验之外:还要有一层“业务校验”

类型对了不代表逻辑对了。以下几类问题 Zod 管不了,需要在数据层补一个显式检查:

问题 表现 处理方式
分类未注册 文章出现在列表但无对应栏目页 在 schema 中用 refine 拦截
标签拼写不一致 同一主题分裂成多个标签 定期检查标签列表,合并同义标签
日期排序异常 置顶失效、顺序奇怪 排序函数里显式处理 pinned 与日期
摘要缺失 列表里摘要为空 从正文自动截取,保证有兜底

排序规则也要写死在一处

列表、归档、上下篇导航都依赖顺序。如果每处各写一套 sort,早晚会出现“三处顺序不一致”的诡异现象。正确做法是把规则集中到一个函数:

export function comparePosts(a: Post, b: Post): number {
  if (a.data.pinned !== b.data.pinned) return a.data.pinned ? -1 : 1;
  return b.data.date.valueOf() - a.data.date.valueOf();
}

所有页面都调用同一个函数,规则改变时只需要改一处。这是“可维护”最朴素也最有效的做法。

小结

内容校验的价值不在于“更严谨”,而在于降低心理负担:你可以放心地隔几个月再写一篇,不必回忆当初定的规矩,因为工具会替你记住。

信息来源:OldFish ·本站原创 · 发布时间:

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。