构建失败排查:一行报错卡了半小时

半路手记 · 踩坑实录


报错

新写了一篇博客文章,放进内容目录,跑构建。秒挂。

[InvalidContentEntryDataError] blog → my-new-post data does not match collection schema.
summary: Required

报错信息说:这篇文章的数据不符合集合的 schema,缺少 summary 字段。

看起来很明确对吧?但这条报错把我带偏了半小时。

排查

我的第一反应是打开新文章看 frontmatter:

---
title: 我的新文章
date: 2026-07-14
tags: [随笔, 经验]
---

确实没有 summary。但是——我翻了翻其他文章,好几篇也没有 summary 字段,它们不是好好地构建成功了吗?

于是我开始在其他方向上找原因:是不是 tags 的问题?日期格式的问题?文件名有非法字符?

反复对比新旧文章的 frontmatter,逐字段排查,花了半小时。

根因

最终我打开了 content.config.ts——Astro 定义内容集合 schema 的配置文件。

const blogSchema = z.object({
  title: z.string(),
  date: z.coerce.date(),
  summary: z.string(),
  tags: z.array(z.string()).default([]),
});

schema 里清清楚楚写着:summary: z.string()——必填。

那为什么旧文章没有 summary 也能通过?

回去仔细看旧文章的 frontmatter——它们确实有 summary 字段,只是我之前扫一眼的时候忽略了。它们长这样:

---
title: 某某文章
date: 2026-07-10
summary: 一句话描述这篇文章的内容
tags: [技术, 经验]
---

我以为旧文章没有 summary,实际上是我看漏了。用「其他文件能过」来反推规则,本身就是不可靠的调试方法。

真正该做的第一步

遇到这类报错,正确的做法不是看报错信息然后去对比其他文件,而是:

直接打开 schema 定义文件,看规则本身。

报错信息告诉你「不匹配」,但不告诉你「应该是什么」。其他文件的 frontmatter 是规则的实例,不是规则本身。只有 schema 定义才是唯一的真相源。

这一步看似显而易见,但在实际操作中很容易被跳过。因为人的本能是「对比」——拿一个能用的和一个不能用的放在一起看,找不同。这在大多数调试场景下有效,但在 schema 校验的场景下会失效:你用来对比的「好的样本」可能本身就有你不知道的字段。

调试清单

后来我给自己列了一套针对 Astro 构建报错的排查流程:

1. 看报错信息,定位文件和集合。

报错会告诉你哪个文件、哪个集合出了问题。这是唯一的确定信息。

2. 打开 content.config.ts,看对应集合的 schema。

哪些字段是必填的(没有 .optional().default()),哪些有默认值。这一步比对比其他文件可靠得多。

3. 改完立刻构建。

不要攒着。新增内容后立刻 npm run build,报错越早出现越好修。

4. 如果报错信息不够明确,开 verbose 模式。

npm run build -- --verbose

Astro 的 verbose 模式会打印更详细的校验信息,告诉你是哪个字段缺失还是类型不匹配。

一个隐蔽的坑

这里还有一个值得注意的点:Astro 在构建时会校验集合里的所有文件,不只是你新加的那一篇。

这意味着:如果某篇旧文章也缺了必填字段,但你上次构建之后没碰过它,那个错误不会在那次构建里出现。它会藏在你下一次构建的报错里——而你可能误以为是新文件导致的问题。

在这个 case 里,新文章确实是直接原因。但在更大的项目里,这种「延迟暴露」的 schema 错误很容易造成误判。你以为是新代码引入了 bug,其实是旧代码一直在违规,只是还没被构建扫到。

一句话

报错说 schema 不匹配,第一反应不是看其他文件的 frontmatter,是打开 schema 定义文件。别从实例推规则,去看规则本身。