暂不支持移动端访问

Astro Content Collections 文章系统

1653 字 8 分钟
AI 摘要

说明文章集合、Zod Schema、Frontmatter、Markdown/MDX loader 和公开文章过滤的完整关系。

AstroBlog 的文章不是页面目录里的散落 Markdown,而是由 Content Collections 管理的一组有 Schema 的内容数据。文章文件负责正文和 Frontmatter,src/content.config.ts 负责定义字段和 loader,src/utils/content-utils.ts 负责公开性、排序和派生关系,页面只消费已经校验过的集合条目。

posts 集合的 loader#

文章集合使用 glob() 读取 src/content/posts/ 下的 Markdown 和 MDX 文件,并排除 _fixtures

TypeScript
const postsCollection = defineCollection({
// 回归测试样本不能进入公开文章集合。
loader: glob({
pattern: ["**/*.{md,mdx}", "!_fixtures/**/*.{md,mdx}"],
base: "./src/content/posts",
}),
schema: z.object({
// 标题和发布日期是生成文章路由所需的最小字段。
title: z.string(),
published: z.date(),
draft: z.boolean().optional().default(false),
tags: z.array(z.string()).optional().default([]),
category: z.string().optional().default(""),
}),
});

实际 Schema 还包含 updateddescriptionimagelanglayouttoccoverAltpinnedauthor、许可证字段、commentpasswordorder,以及由内容查询函数写入的上一篇/下一篇字段。必填字段缺失或类型不匹配时,构建阶段会直接报错,而不是生成字段不完整的页面。

content.config.ts 同时定义动态、番组、生活记录、相册、导航、友链、更新日志等集合。可选集合使用空 loader 处理目录为空的情况,因此某个功能没有内容时,页面可以显示空状态而不让整个站点构建失败。

Frontmatter 是数据契约#

文章文件的 Frontmatter 会被解析成类型化对象。日期必须能够被 Astro 转换为 Date,标签必须是字符串数组,layout 只能是 standardwidepinneddraft 必须是布尔值。后台编辑器如果写出字符串形式的 "false",就可能与 Schema 预期的布尔值不同。

文章内容可以使用 Markdown,也可以使用 MDX。两者都进入同一个 unified 管线,区别在于 MDX 允许更复杂的组件表达。正文中的图片、代码块、公式、Mermaid、提示框和 GitHub 指令由 remark/rehype 插件统一处理,页面组件无需为每种语法单独写解析器。

草稿和 fixture 的两层过滤#

测试文章分为两类。_fixtures/ 下的文件用于排版、媒体和代码回归,在 collection loader 层直接排除;正式目录中的系统测试文章可以保留在集合中,但通过 draft: true 隐藏。公开查询再调用 getPublicPosts()

TypeScript
export async function getPublicPosts() {
publicPostsPromise ??= getCollection("posts").then((posts) =>
// loader 负责隔离 fixture,这一层继续过滤草稿文章。
posts.filter(
(post) => post.data.draft !== true && !isFixtureContentId(post.id),
),
);
return [...(await publicPostsPromise)];
}

这两层过滤解决了不同问题:loader 防止回归样本进入生产集合,draft 允许文章文件存在但暂时不公开。文章列表为空时,应依次检查文件扩展名、Frontmatter 是否解析成功、是否误设 draft: true、是否放进 _fixtures,再检查部署使用的内容版本。

内容查询和缓存#

getPublicPosts()getPublicMoments()getPublicAlbums() 使用模块级 Promise 缓存,避免同一次构建或页面生成过程中反复读取集合。函数返回新数组,调用方可以排序而不改变缓存中的原始数组。新增过滤条件时,应保持这个特性,否则一个页面的排序可能影响另一个页面。

文章字段中的 categorytags 会经过 taxonomy 工具规范化,确保不同大小写或空白不会生成重复分类。修改 taxonomy 规则会影响分类页、标签页、归档和搜索,必须运行相关内容检查。

从集合到正文#

详情页根据 slug 找到 CollectionEntry<"posts">,然后调用 Astro 的 render(entry)。渲染结果包含正文组件和 headings;布局把 headings 交给目录,把 metadata 交给文章 Hero、页脚和 SEO。阅读时间、摘要和 Mermaid 等信息在构建阶段产生,因此浏览器不需要再次解析整篇 Markdown。

文章图片是一个例外:相对路径需要结合 entry 的文件位置解析,交给 src/plugins/article-images/ 和图片工具处理。正文组件只接收已经转换过的图片节点,不应在渲染时拼接磁盘路径。

新增字段的正确步骤#

新增一个 Frontmatter 字段时,先修改 posts Schema,再更新已有文章和 CollectionEntry 的使用位置;如果后台需要编辑,再修改 WriteEditor.svelteadminContent.ts 和对应验证脚本;如果字段影响卡片或 SEO,再修改 PostCard.astro、详情页和 JSON-LD。字段只加在 Schema 而不处理消费方,会让它能写但没有任何显示效果。

新增一个内容集合时,需要定义 loader、Schema、导出注册、查询函数和页面路由,并决定是否允许空集合。不要复用文章 Schema 处理结构完全不同的数据,否则后续字段会变成大量可选值,失去校验的价值。

排查命令#

Terminal windowpowershell
# 先验证集合 Schema 和 Astro 模板类型。
pnpm check
# 检查后台生成的 Frontmatter 与文章草稿隔离。
pnpm verify:admin
# 通过完整构建验证 Markdown 管线、静态路由、图片和搜索索引。
pnpm build

pnpm check 先确认集合和 Astro 类型没有错误;verify:admin 检查后台生成内容是否符合 Frontmatter 契约;完整构建会进一步验证 Markdown 管线、图片、路由、页面生成和搜索索引。文章显示问题应从这些入口逐层定位,不要直接在列表模板中增加绕过过滤的特殊分支。

字段分类和影响面#

文章字段可以按影响范围分成四类:

类型字段示例主要消费者
路由和可见性draftpasswordloader、公开查询、详情页
列表和排序publishedpinnedorder列表、归档、上一篇/下一篇
展示元数据titledescriptionimagecoverAlt卡片、Hero、SEO、RSS
正文行为layouttoccomment、许可证字段详情布局、目录、评论、页脚

新增字段前先确定它属于哪一类。比如把 featured 当成 pinned 的别名,会让排序和首页推荐出现两套含义;更好的做法是明确字段用途,并在查询层建立唯一规则。

Markdown 管线的调试方法#

遇到公式、Mermaid、提示框或图片异常时,不要从最终 HTML 猜原因。先确认 remark 阶段是否生成了预期节点,再确认 rehype 阶段是否消费了它。remarkExcerpt 生成摘要,remarkReadingTime 生成阅读时间,rehypeSlugrehypeAutolinkHeadings 生成标题 ID 与锚点;它们的结果会被详情布局、目录和搜索同时使用。调整其中一个插件的输入结构,可能影响多个页面。

MDX 文章可以导入组件,但仍然必须遵守文章集合的 Frontmatter Schema。组件只改变正文渲染,不会绕过 draft、分类或发布日期校验。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 目录 ]
[ 全部文章 ]