Day16 - Astro Series: Content Collection

Astro 系列文第十六日:內容集合

一個漂亮的漸層背景上面有一句標題:「內容集合」

前言

除了定義資料在元件中、在 src 中 import 進來或者是 fetch 遠端資料之外有其他撰寫內容的方式嗎?有的!

什麼是內容集合 Content Collection?

內容集合於 2.0 版本推出,用於替網站的本地內容提供易於使用管理、自動化型別驗證的功能。如果你有大量文件需要注入網頁中便可以使用該項功能。

怎麼使用內容集合?

在介紹資料夾結構的章節中提到 src/content 是被保留的資料夾,其用途是用於存放內容集合相關的文件與設定,此外不能用做其他用途。首先可以創造一個條目資料夾,並且在裡面存放與該條目相關的文件,文件可以是 Markdown 或 MDX 甚至是 YAML 或 JSON:

src/content/
├── newsletter/
│ ├── week-1.md
│ └── week-2.md
├── blog/
│ ├── post-1.md
│ └── post-2.md
└── authors/
├── grace-hopper.json
└── alan-turing.json

當創建好條目與其內容文件之後就可以透過 Astro 提供的 API 查詢。

什麼是 .astro 資料夾?

在使用內容集合時會發現 Astro 會為內容集合的設定自動生成相關的檔案,這些檔案會被放置在 .astro 資料夾中,不需要做任何的設定或修改,只要執行 astro dev或 astro build 就會自動生成,也可以手動執行 astro sync 來生成。如果你有使用 Git 管理專案,建議將該資料夾給寫入 .gitignore 設定當中,避免被記錄下來。

定義集合

內容集合的設定是可選的,增加額外的集合設定將會更好的幫助 Astro 驗證資料的型別。要定義集合就需要創建 src/content/config.ts 文件(也可以是 .js 或 .mjs),基本內容如下:

// 1. 導入 `astro:content` 提供的工具函式
import { defineCollection } from 'astro:content';
// 2. 定義集合
const blogCollection = defineCollection({
/* ... */
});
// 3. 輸出一個 `collections` 物件用於註冊集合,名稱應與條目資料夾相同
export const collections = {
blog: blogCollection,
};

透過定義集合型別可以強制集合內資料的準確與一致性,當有違背規則的情況 Astro 將會提供錯誤與建議,以下是一個集合物件的範例:

import { z, defineCollection } from 'astro:content';
defineCollection({
type: 'content', // v2.5.0 版本新增,註明資料種類是 Markdown 還是像 JSON 或 YAML 的格式 (content / data)
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});

當集合內的資料是 Markdown 時 type 為 content,當是 JSON 或 YAML 時則是 data。 schema 的設置可以參考 Zod 文件🔗,Astro 使用 Zod 來為資料做檢核。

定義多個集合

可以創造多個集合之後再放入 collections 物件中。

const blogCollection = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({
/* ... */
}),
});
const authors = defineCollection({
type: 'data',
schema: z.object({
/* ... */
}),
});
export const collections = {
blog: blogCollection,
newsletter: newsletter,
authors: authors,
};

查詢集合

Astro 提供兩個函式用於查詢一個或多個內容集合,分別是:getCollection🔗 與 getEntry🔗。

import { getCollection, getEntry } from 'astro:content';
// 獲取所有內容集合
// 需要集合的名稱作為參數
// 舉例來說: `src/content/blog/**`
const allBlogPosts = await getCollection('blog');
// 獲取單個條目從集合之中
// 需要集合的名稱以及條目 `slug` (內容集合)或 `id`(資料集合)
const graceHopperProfile = await getEntry('authors', 'grace-hopper');

篩選集合

getCollection 接受一個「過濾用」的回呼函式,用於過濾搜尋內容,像以下的案例:「當在 Production 環境時,如果該筆項目 draft 非為 true才顯示,在非 Production 則顯示一切資料」

import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

顯示集合內容

透過 map 來遍歷並回傳每個項目的 Markup,我們能將撈取到的資料製作成一個清單並呈現到頁面當中。

---
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
{blogEntries.map(blogPostEntry => (
<li>
<a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
<time datetime={blogPostEntry.data.publishedDate.toISOString()}>
{blogPostEntry.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>

運用集合內容產生 SSG Route

一樣透過 getStaticPaths 這個方法,不過這次使用的資料不像之前一樣是寫死在元件內的,而是透過集合內容的資料來建立。

src/pages/posts/[...slug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
// 1. 抓取 blog 集合
const blogEntries = await getCollection('blog');
// 2. 回傳個別的 Route 名稱與 Props 用於產生 Route
return blogEntries.map(entry => ({
params:{ slug: entry.slug }, props: { entry },
}));
}
// 3. 再透過解構 Props 得到個別集合項目中的內容並顯示出來
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />

總結

最後會建議實際動手練習,如果過程中有問題可以參考看看我的範例🔗:

  • 使用 Astro 提供的 defineCollection 來創建自己理想的集合並定義其資料型態
  • 在 content 資料夾中撰寫建立相關檔案
  • 使用 getCollection 抓取並顯示在頁面當中
  • 使用 getCollection 生成 Route

延伸閱讀

Content Collections🔗 - Astro DOCS