# Astroブログのタグ・著者の参照切れをCIで検出する

> Astroのcontent collectionsで記事の著者やタグを参照していますが、idを打ち間違えてもビルドは通ってしまいます。reference()の挙動を確かめて、参照切れをCIで弾くようにした話です。

- 公開日: 2026-06-28
- 著者: 村井 謙太
- タグ: Astro, CI/CD
- URL: https://tech.anycloud.co.jp/articles/astro-content-reference-check

---

このブログは Astro の content collections で記事を管理しています。記事の frontmatter には、著者とタグをこう書きます。

```yaml
author: murai-kenta
tags: ["astro", "ci-cd"]
```

`author` は `content/authors/murai-kenta.json`、タグは `content/tags/<id>.json` を指していて、`content.config.ts` の中で `reference()` を使ってコレクション同士をつないでいます。

記事を追加していたとき、ふと不安になりました。この `author` を、`murai-kenta` のつもりで `murai-genta` と打ち間違えたらどうなるんだろう、と。型でつながっているように見えるので、ビルドで弾かれて気づけるだろう、と思っていました。

念のため、存在しない著者とタグを書いた記事を1本でっち上げて試してみます。

```yaml
author: "ghost-author"
tags: ["this-tag-does-not-exist"]
```

`astro check` も `astro build` も、何事もなく通りました。記事ページも普通に生成されます。エラーも警告も出ません。

## なぜ通ってしまうのか

ドキュメントを読むと、`reference()` の挙動が書いてありました。

> Validation of referenced entries happens at runtime when using `getEntry()` or `getEntries()`: if a referenced entry is invalid, this will return undefined.

`reference()` が見ているのは「id が文字列か」までで、その id が実在するかどうかは確認していません。実在は `getEntry()` で取り出すときに初めて分かり、見つからなければ例外ではなく `undefined` が返ってくるだけです。

記事ページ側のコードは、その `undefined` を素直に受け流していました。

```ts
const author = await getEntry(article.data.author);
const tags = (await Promise.all(article.data.tags.map((t) => getEntry(t))))
  .filter((t) => t != null);
```

著者が `undefined` なら、著者欄は空のまま表示されます。タグは `filter` で落とされるので、打ち間違えたタグはページから消えます。どちらも見た目が少し寂しくなるだけで、ビルドは成功扱いのままです。

挙動としては合理的だと思います。参照先がまだ無い状態でも開発を止めない、という割り切りなのでしょう。ただ、記事が増えてくると、著者欄が空のまま公開されていても自分では気づきにくくなります。

## 実在チェックを自分で足す

調べたところ、Astro には参照の実在を強制するオプションは見当たりませんでした。なので簡単なチェックを自前で書きます。やることは「全記事の `author` と `tags` の id が、`content/authors` と `content/tags` に実在するか」を突き合わせるだけです。frontmatter の読み取りには、もともと依存に入っていた `gray-matter` を使いました。

```ts
const { data } = matter(fs.readFileSync(mdPath, "utf-8"));
if (!authorIds.has(String(data.author))) {
  errors.push(`${slug}: author "${data.author}" が存在しません`);
}
for (const tag of data.tags ?? []) {
  if (!tagIds.has(String(tag))) {
    errors.push(`${slug}: tag "${tag}" が存在しません`);
  }
}
// errors があれば最後に process.exit(1)
```

このブログには元から簡単な検証スクリプト群があったので、その一つとして足して、PRのCIで動くようにしました。さきほどの捨て記事で実行すると、こう出ます。

```
✗ 参照切れ 2件:
  __ref_test: author "ghost-author" が content/authors/ に存在しません
  __ref_test: tag "nope-not-real" が content/tags/ に存在しません
```

既存の記事はすべて素通りで、間違っているものだけが指摘されます。これで、著者IDを打ち間違えたまま公開してしまう事故は防げるようになりました。

## まとめ

Astro の content collections で `reference()` を使うときは、「型はつながっているが、参照先の実在まではビルドで保証されない」と覚えておくと安全です。特に著者やカテゴリのような「あって当たり前」の参照ほど、typo が静かに通り抜けます。

実在チェック自体は数十行で書けるので、記事が増えていくブログなら、早めに一つ用意しておくと後が楽です。
