用 Astro 内容集合给个人站做内容模型
系列:个人站搭建
为什么我放弃 Halo 和 WordPress,改用 Markdown 加 Zod 校验;以及内容集合怎么把"写错字段"变成构建期错误而不是线上事故。
个人站的第一个决定不是选主题,是决定内容存在哪里。
试过的两条路
一开始我认真考虑过 Halo 和 WordPress。它们的共同卖点是“后台管理界面”——打开浏览器就能写,写完点发布。这对非开发者是刚需。
但对我不是。我是前端,真正的需求是:内容归我所有、格式是纯文本、改样式不用求主题作者。后台界面反而是一层多余的抽象。
| 方案 | 内容形态 | 我放弃它的原因 |
|---|---|---|
| Halo | 数据库 | 主题是 Thymeleaf 模板,改外观不如自己写组件 |
| WordPress | 数据库 | 插件是最大攻击面,得长期打补丁 |
| Markdown 文件 | 纯文本 | 就是它了 |
内容集合解决了什么
Astro 的内容集合允许你给每类内容声明一个 schema。这不是“文档约定”,是构建时会真的执行的校验:
const postSchema = z.object({
title: z.string().max(120),
description: z.string().max(300),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
});
好处很具体。以前我写 Hexo 时,忘了写 date 字段,文章会安静地跑到列表最后或者干脆消失,我要翻半天才找到原因。现在同样的情况直接构建失败,并且告诉你哪个文件哪个字段。
一个容易忽略的收益:草稿
draft: true 这一条字段,配合构建期过滤,意味着我可以把没写完的文章跟其他内容一起提交进仓库,而它不会出现在线上任何地方——列表、归档、标签页、RSS、sitemap 全都不含它。
这比“写完再提交”舒服得多:工作可以中断,进度留在版本控制里。
最后一个决定:slug 冲突要炸
URL 是个人站最不该坏的东西。所以我让 slug 冲突直接使构建失败,而不是后来者静默覆盖前者:
const conflicts = [...seen.entries()].filter(([, ids]) => ids.length > 1);
if (conflicts.length > 0) throw new Error('slug 冲突,构建中止');
宁可构建失败一次,也不要线上的链接悄悄指向了另一篇文章。