用 Astro Content Collections 管理部落格文章

  • Astro

這個部落格的每一篇文章,都只是 src/content/blog/ 裡的一個 Markdown 檔。沒有資料庫,沒有後台,寫完 push 就上線。背後靠的是 Astro 的 Content Collections。

定義集合

所有設定集中在 src/content.config.ts:

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

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

兩個重點:

  1. glob loader 是 Astro 5 之後的 Content Layer 寫法,直接宣告「哪個資料夾下的哪些檔案屬於這個集合」,比舊版隱含約定清楚得多。
  2. Zod schema 讓 frontmatter 有型別保障。pubDatez.coerce.date(),所以在 Markdown 裡寫 2026-07-09 這種字串就會自動轉成 Date;忘了填 title 則會在建置時直接報錯,而不是上線後才發現版面壞掉。

草稿機制

draft 欄位預設 false,列表頁查詢時把它濾掉:

const posts = (await getCollection('blog'))
  .filter((post) => !post.data.draft)
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

想先寫一半存起來,只要 frontmatter 加上 draft: true,檔案照樣進版本控制,但不會出現在網站上。

RSS 與 sitemap 順便解決

因為文章都是結構化資料,RSS 就只是把同一份查詢餵給 @astrojs/rss,sitemap 則交給 @astrojs/sitemap 在建置時自動產生。內容只有一份,出口可以有很多個——這是我最喜歡 Content Collections 的地方。