用 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 };
兩個重點:
globloader 是 Astro 5 之後的 Content Layer 寫法,直接宣告「哪個資料夾下的哪些檔案屬於這個集合」,比舊版隱含約定清楚得多。- Zod schema 讓 frontmatter 有型別保障。
pubDate用z.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 的地方。