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