可选:创建一个内容集合
现在你已经有了一个使用 Astro 内置的基于文件的路由的博客,你将更新它以使用内容集合。内容集合是管理类似内容组(例如博客文章)的强大方式。
准备好去……
- 将你的博客文章文件夹移动到
src/blog/
- 创建一个 schema 来定义你的博客文章 frontmatter
- 使用
getCollection()
获取博客文章内容和元数据
学习:页面与内容集合
标题为“学习:页面与内容集合”的部分即使在使用内容集合时,你仍然会使用 src/pages/
文件夹来存放单个页面,例如“关于我”页面。但是,将博客文章移出这个特殊的文件夹,可以让你使用更强大、性能更好的 API 来生成博客文章索引和显示单篇博客文章。
同时,你会在代码编辑器中获得更好的指导和自动补全,因为你将拥有一个schema 来为每篇文章定义一个通用结构,Astro 将通过 Zod(一个用于 TypeScript 的 schema 声明和验证库)帮助你强制执行该结构。在你的 schema 中,你可以指定 frontmatter 属性何时是必需的(例如 description 或 author),以及每个属性必须是哪种数据类型(例如字符串或数组)。这有助于更早地发现许多错误,并提供描述性的错误消息,准确地告诉你问题所在。
在我们的指南中阅读更多关于Astro 的内容集合的信息,或者按照下面的说明开始,将一个基本的博客从 src/pages/posts/
转换为 src/blog/
。
知识测验
标题为“测试你的知识”的部分-
你可能会把哪种类型的页面保留在
src/pages/
中? -
哪项不是将博客文章移至内容集合的好处?
-
内容集合使用 TypeScript ……
下面的步骤向你展示了如何通过为博客文章创建一个内容集合来扩展“构建博客”教程的最终产品。
升级依赖
标题为“升级依赖”的部分通过在终端中运行以下命令,升级到 Astro 的最新版本,并将所有集成升级到其最新版本
# Upgrade Astro and official integrations togethernpx @astrojs/upgrade
# Upgrade Astro and official integrations togetherpnpm dlx @astrojs/upgrade
# Upgrade Astro and official integrations togetheryarn dlx @astrojs/upgrade
为你的文章创建一个内容集合
标题为“为你的文章创建一个内容集合”的部分-
创建一个名为
src/blog/
的新 内容集合(文件夹)。 -
将所有现有的博客文章(
.md
文件)从src/pages/posts/
移动到这个新的内容集合中。 -
创建一个
src/content.config.ts
文件,为你的postsCollection
定义一个 schema。对于现有的博客教程代码,将以下内容添加到文件中,以定义其博客文章中使用的所有 frontmatter 属性src/content.config.ts // Import the glob loaderimport { glob } from "astro/loaders";// Import utilities from `astro:content`import { z, defineCollection } from "astro:content";// Define a `loader` and `schema` for each collectionconst blog = defineCollection({loader: glob({ pattern: '**/[^_]*.md', base: "./src/blog" }),schema: z.object({title: z.string(),pubDate: z.date(),description: z.string(),author: z.string(),image: z.object({url: z.string(),alt: z.string()}),tags: z.array(z.string())})});// Export a single `collections` object to register your collection(s)export const collections = { blog }; -
为了让 Astro 识别你的 schema,请退出(
CTRL + C
)并重启开发服务器以继续本教程。这将定义astro:content
模块。
从一个内容集合生成页面
标题为“从一个内容集合生成页面”的部分-
创建一个名为
src/pages/posts/[...slug].astro
的页面文件。当你的 Markdown 和 MDX 文件位于内容集合中时,它们不再通过 Astro 的基于文件的路由自动成为页面,因此你必须创建一个负责生成每个独立博客文章的页面。 -
添加以下代码来查询你的内容集合,以使每篇博客文章的 slug 和页面内容对它将生成的每个页面都可用
src/pages/posts/[...slug].astro ---import { getCollection, render } from 'astro:content';export async function getStaticPaths() {const posts = await getCollection('blog');return posts.map(post => ({params: { slug: post.id }, props: { post },}));}const { post } = Astro.props;const { Content } = await render(post);--- -
在 Markdown 页面的布局中渲染你的文章
<Content />
。这允许你为所有文章指定一个通用的布局。src/pages/posts/[...slug].astro ---import { getCollection, render } from 'astro:content';import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';export async function getStaticPaths() {const posts = await getCollection('blog');return posts.map(post => ({params: { slug: post.id }, props: { post },}));}const { post } = Astro.props;const { Content } = await render(post);---<MarkdownPostLayout frontmatter={post.data}><Content /></MarkdownPostLayout> -
删除每篇文章 frontmatter 中的
layout
定义。你的内容现在在渲染时被布局包裹,因此不再需要此属性。src/content/posts/post-1.md ---layout: ../../layouts/MarkdownPostLayout.astrotitle: 'My First Blog Post'pubDate: 2022-07-01...---
用 getCollection()
替换 import.meta.glob()
标题为“用 getCollection() 替换 import.meta.glob()”的部分-
在你所有展示博客文章列表的地方,比如教程的博客页面(
src/pages/blog.astro/
),你需要将import.meta.glob()
替换为getCollection()
来从 Markdown 文件中获取内容和元数据。src/pages/blog.astro ---import { getCollection } from "astro:content";import BaseLayout from "../layouts/BaseLayout.astro";import BlogPost from "../components/BlogPost.astro";const pageTitle = "My Astro Learning Blog";const allPosts = Object.values(import.meta.glob("../pages/posts/*.md", { eager: true }));const allPosts = await getCollection("blog");--- -
你还需要更新对每个
post
返回数据的引用。现在你可以在每个对象的data
属性上找到你的 frontmatter 值。此外,使用内容集合时,每个post
对象将有一个页面slug
,而不是一个完整的 URL。src/pages/blog.astro ---import { getCollection } from "astro:content";import BaseLayout from "../layouts/BaseLayout.astro";import BlogPost from "../components/BlogPost.astro";const pageTitle = "My Astro Learning Blog";const allPosts = await getCollection("blog");---<BaseLayout pageTitle={pageTitle}><p>This is where I will post about my journey learning Astro.</p><ul>{allPosts.map((post) => (<BlogPost url={post.url} title={post.frontmatter.title} />)}<BlogPost url={`/posts/${post.id}/`} title={post.data.title} />))}</ul></BaseLayout> -
该教程博客项目还使用
src/pages/tags/[tag].astro
为每个标签动态生成一个页面,并在src/pages/tags/index.astro
显示标签列表。将上述相同的更改应用于这两个文件
- 使用
getCollection("blog")
而不是import.meta.glob()
来获取关于所有博客文章的数据 - 使用
data
而不是frontmatter
来访问所有 frontmatter 值 - 通过将文章的
slug
添加到/posts/
路径来创建页面 URL
生成单个标签页面的页面现在变为
src/pages/tags/[tag].astro ---import { getCollection } from "astro:content";import BaseLayout from "../../layouts/BaseLayout.astro";import BlogPost from "../../components/BlogPost.astro";export async function getStaticPaths() {const allPosts = await getCollection("blog");const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];return uniqueTags.map((tag) => {const filteredPosts = allPosts.filter((post) =>post.data.tags.includes(tag));return {params: { tag },props: { posts: filteredPosts },};});}const { tag } = Astro.params;const { posts } = Astro.props;---<BaseLayout pageTitle={tag}><p>Posts tagged with {tag}</p><ul>{ posts.map((post) => <BlogPost url={`/posts/${post.id}/`} title={post.data.title} />) }</ul></BaseLayout>自己试试 - 更新标签索引页面中的查询
标题为“自己试试 - 更新标签索引页面中的查询”的部分导入并使用
getCollection
来获取src/pages/tags/index.astro
页面上博客文章中使用的标签,遵循与上面相同的步骤。给我看代码。
src/pages/tags/index.astro ---import { getCollection } from "astro:content";import BaseLayout from "../../layouts/BaseLayout.astro";const allPosts = await getCollection("blog");const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];const pageTitle = "Tag Index";---<!-- ... --> - 使用
更新 frontmatter 值以匹配你的 schema
标题为“更新 frontmatter 值以匹配你的 schema”的部分如有必要,请更新项目中任何与你的内容集合 schema 不匹配的 frontmatter 值,例如在你的布局中。
在博客教程示例中,pubDate
是一个字符串。现在,根据为文章 frontmatter 定义类型的 schema,pubDate
将是一个 Date
对象。你现在可以利用这一点,使用任何 Date
对象可用的方法来格式化日期。
要在博客文章布局中渲染日期,请使用 toLocaleDateString()
方法将其转换为字符串
<!-- ... --><BaseLayout pageTitle={frontmatter.title}> <p>{frontmatter.pubDate.toLocaleDateString()}</p> <p><em>{frontmatter.description}</em></p> <p>Written by: {frontmatter.author}</p> <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} /><!-- ... -->
更新 RSS 函数
标题为“更新 RSS 函数”的部分该教程博客项目包含一个 RSS 订阅源。此函数也必须使用 getCollection()
从你的博客文章中返回信息。然后,你将使用返回的 data
对象生成 RSS 项目。
import rss from '@astrojs/rss';import { pagesGlobToRssItems } from '@astrojs/rss';import { getCollection } from 'astro:content';
export async function GET(context) { const posts = await getCollection("blog"); return rss({ title: 'Astro Learner | Blog', description: 'My journey learning Astro', site: context.site, items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')), items: posts.map((post) => ({ title: post.data.title, pubDate: post.data.pubDate, description: post.data.description, link: `/posts/${post.id}/`, })), customData: `<language>en-us</language>`, })}
有关使用内容集合的博客教程的完整示例,请参阅教程仓库的Content Collections 分支。
教程