iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

在 Astro 中,把 .md 丟進 src/pages 就能直接產生對應網址。頁面只有三五篇時,這種檔案即路由(file-based routing)非常省事。

但當內容增加到幾十篇時,只靠 src/pages 就會遇到瓶頸:每篇 frontmatter 欄位缺乏約束、檔案路徑硬綁網址結構,而且沒辦法直接用程式一次載入所有文章來做排序、分頁或 RSS。散在路由資料夾裡的檔案,缺少了一層統一的內容模型(Content Model)。

Astro 的解法是 Content Collections:內容維持 Git 版控的純文字檔案,但在建置階段透過 Schema 進行型別校驗,並提供統一的 API 供頁面查詢與渲染。

只丟 src/pages,少了什麼?

src/pages 適合單純的獨立頁面(如關於我們、隱私權政策)。若拿來放大量文章,會撞上三個限制:

  • 缺乏欄位約束:每篇 frontmatter 各自為政,這篇寫 date、那篇寫 pubDate,甚至漏填欄位,開發時都不會報錯,直到正式上線頁面渲染出空值或破版。(在 build 階段自動擋下欄位錯誤的做法見 Day 12。)
  • 檔案位置綁死網址:想調整 URL 階層或將文章移至子目錄集中管理,就必須連帶更動檔案路徑與外部連結。
  • 無法以整體方式查詢資料:想製作文章列表、依發布日期排序或過濾標籤,就得自行讀取檔案系統並解析 frontmatter。

Content Collections 將這些散落的內容檔抽離路由,宣告為帶有 Schema 的資料集合:內容路徑與網址脫鉤、欄位在 build 階段具備型別校驗,頁面也能透過統一 API 一次撈出整包資料。

一個 collection 的最小實作

建立一個 Collection 包含三個步驟:在設定檔宣告 Schema、在指定目錄放置內容檔、在頁面查詢並渲染。

步驟 1:宣告 Collection(src/content.config.ts

Astro v6 統一在 src/content.config.ts 定義內容集合(注意舊版 Astro 的路徑為 src/content/config.ts):

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

const blog = defineCollection({
  // loader 定義內容來源:glob 會載入指定目錄下的 .md 與 .mdx
  loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    day: z.number().int().positive(), // 鐵人賽 Day 編號,方便排序
    pubDate: z.coerce.date(),         // 自動將字串轉為 Date 物件
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

這段設定包含兩個核心:

  • loader:指定內容來源。這裡使用內建的 glob loader 載入 src/content/blog 下的檔案。Day 11 會介紹如何載入 JSON 或外部 API 資料。
  • schema:使用 Zod 定義 frontmatter 欄位規則。若某篇文章的 frontmatter 型別不符或缺少必填欄位,build 階段就會直接拋錯中斷。

註:官方文件範例寫 import { z } from 'astro/zod',這裡使用 import { z } from 'astro:content',兩者皆可(Astro 已 re-export Zod)。本文基準為 Astro v6。

步驟 2:放置內容檔

文章存放在 src/content/blog/ 目錄下,開頭透過 frontmatter 填寫 Schema 規範的欄位:

---
title: 為什麼前端工程師要重新看 Astro
description: 從 server-first、content-first 與 islands 建立整個系列的技術主張。
day: 1
pubDate: 2026-06-03
tags: [astro, mental-model, islands]
---
正文從這裡開始……

步驟 3:在頁面查詢並渲染

在頁面中透過 getCollection 取得整組文章,再呼叫 render 將單篇文章轉換為 HTML 元件:

---
import { getCollection, render } from 'astro:content';

export const prerender = true; // 文章頁在 build 時先產生好靜態檔案

export async function getStaticPaths() {
  const posts = await getCollection('blog', ({ data }) => !data.draft);
  return posts.map((post) => ({
    params: { slug: post.id }, // v6 統一使用 post.id
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await render(post); // v6 為 render(entry),非 entry.render()
---
<h1>{post.data.title}</h1>
<Content />

完成上述設定後,post.data 的所有欄位都享有 TypeScript 型別支援與編輯器自動補全。

在我們的實作專案中,首頁文章列表改由 getCollection('blog') 讀取並依 day 排序:

實作專案首頁文章列表由 Content Collections 產生,Day 1、Day 8、Day 10 依 day 欄位排序

文章詳情頁則由動態路由 src/pages/blog/[...slug].astro 統一渲染,內容檔案的存放位置不再直接綁死 URL。

Schema 的價值在輸入錯誤時尤為明顯。若刻意在 Day 10 的 frontmatter 中漏填 description,執行 build 時會立即中斷並拋出明確錯誤:

[InvalidContentEntryDataError] blog → day-10-schema-error data does not match collection schema.

description: Required

錯誤在建置期就被攔截,避免缺漏欄位的頁面上線後造成破版或靜態生成異常。

內容該放資料庫,還是放檔案?

Content-first 並非放諸四海皆準的方案,它與傳統資料庫驅動架構各有適用的場景:

面向 WordPress(讀資料庫) Astro(Markdown + Schema)
內容存儲 資料庫(需維護 DB 與伺服器主機) Git 版控中的 .md(零資料庫維護成本)
內容修改 即時生效,無需重新部署 需重新 build 與部署(可由 CI/CD 自動化)
編輯介面 內建視覺化後台(WYSIWYG) 無內建後台(需另外整合 Headless CMS)
版控與審查 內容存於資料庫,難以 diff 與版本回溯 支援 Git diff、Pull Request 與完整歷史回溯
產出形態 伺服器動態產生(常需快取外掛輔助) 純靜態 HTML,直接部署至 Edge CDN
資安面向 後台與外掛程式為長期潛在攻擊面 純靜態檔案,攻擊面極小

技術選型取決於網站維護模式:

  • 開發者自營、內容為主、重視版本控管的網站(如技術部落格、產品文檔),Markdown + Schema 在效能、成本、安全與維運上有顯著優勢。
  • 非技術人員高頻編輯、需即時發布或海量內容的網站,傳統資料庫 CMS 或 Headless CMS 仍是更合適的選擇。

關於圖片:純文字內容適合放在 Markdown 檔,圖片則需合適的儲存位置。Astro 內建圖片最佳化管線,少量文章插圖可直接放在原始碼目錄;若圖片量極大或來自外部上傳,實務上通常會搭配專屬圖床(詳見 Day 18)。

三個容易誤解的觀念

  • Collection 在 Build 階段解析,非 Runtime 資料庫:Collection 的資料在建置時讀取並靜態化,適合靜態內容。若需處理使用者即時寫入或動態查詢的資料,應使用關聯式資料庫(詳見 Day 22)。
  • Schema 驗證的是靜態 Frontmatter,非用戶表單輸入:Schema 的職責是在 build 階段確保文章結構符合規範;訪客送出表單的動態驗證則由 Astro Actions 處理(詳見 Day 21)。
  • 內容更新需觸發 Build 與部署:由於內容以檔案形式儲存,修改文章後需重新建置才能生效(實務上由 Git push 觸發 CI/CD 自動部署)。這與傳統 CMS「儲存即發布」的運作模型不同。

下一步

Content Collections 將散落在目錄中的內容檔案轉化為具備 Schema 約束、可被程式靈活查詢的資料集合。

Day 11 會介紹 Collection 的多樣化資料來源:除了本地 Markdown,還能透過不同的 Loader 載入 JSON 或外部 API 資料。Schema 的進階約束與錯誤防禦見 Day 12:Schema 與 Zod 驗證;文章、作者與分類的關聯處理見 Day 13:作者與分類關聯;資料量增加後的檢索與即時搜尋見 Day 14:搜尋與過濾 Island

若專案確實需要資料庫以提供即時編輯介面,Day 28 會深入比較主流 CMS 與 Astro 的整合方案。

本日程式碼:step-10|只看這天的改動:step-09...step-10


上一篇
內容要用純 Markdown 還是 MDX?差在哪、怎麼選?
下一篇
Astro Content Loader:本地 Markdown、JSON、外部資料怎麼進入內容層?
系列文
用 Astro 打造 Content-first 前端網站:30 天從靜態內容到會員、資料庫與選型(3rd)21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言