Day16 - Astro Series: Content Collection

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

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

前言

除了定義資料在元件中、在 srcimport 進來或者是 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 devastro 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 时 typecontent,当是 JSON 或 YAML 时则是 dataschema 的设置可以参考 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