Skip to content

教程 - 通过内容集进行扩展

内容集合是管理类似内容组(例如博客文章)的有效方法。集合有助于组织文档、验证 YAML frontmatter,并为所有内容提供自动 TypeScript 类型安全(即使你自己不编写任何 TypeScript)。

¥Content collections are a powerful way to manage groups of similar content, such as blog posts. Collections help to organize your documents, validate your YAML frontmatter, and provide automatic TypeScript type-safety for all of your content (even if you don’t write any TypeScript yourself).

Get ready to…

  • 将你的博客文章文件夹移至 src/content/

  • 创建一个架构来定义你的博客文章 frontmatter

  • 使用 getCollection() 获取博客文章内容和元数据

¥Prerequisites

你将需要一个现有的 Astro 项目,其中包含 src/pages/ 文件夹中的 Markdown 或 MDX 文件。

¥You will need an existing Astro project with Markdown or MDX files in the src/pages/ folder.

本教程使用 构建博客教程的完成项目 演示如何将博客转换为内容集合。你可以在本地分叉并使用该代码库,或者在 在 StackBlitz 中编辑博客教程的代码 之前在浏览器中完成本教程。

¥This tutorial uses the Build a Blog tutorial’s finished project to demonstrate converting a blog to content collections. You can fork and use that codebase locally, or complete the tutorial in the browser by editing the blog tutorial’s code in StackBlitz.

你可以在自己的 Astro 项目中遵循这些步骤,但你需要调整代码库的说明。

¥You can instead follow these steps with your own Astro project, but you will need to adjust the instructions for your codebase.

我们建议首先使用我们的示例项目来完成这个简短的教程。然后,你可以使用所学知识在自己的项目中创建内容集合。

¥We recommend using our sample project to complete this short tutorial first. Then, you can use what you have learned to create content collections in your own project.

¥Build a Blog Tutorial Code

建立博客入门教程 中,你了解了 Astro 的 内置基于文件的路由src/pages/ 文件夹中任何位置的任何 .astro.md.mdx 文件都会自动成为你网站上的页面。

¥In the Build a Blog introductory tutorial, you learned about Astro’s built-in file-based routing: any .astro, .md, or .mdx file anywhere within the src/pages/ folder automatically became a page on your site.

为了在 https://example.com/posts/post-1/ 创建你的第一篇博客文章,你创建了一个 /posts/ 文件夹并添加了文件 post-1.md。然后,每次你想要向网站添加新博客文章时,你都会向此文件夹添加一个新的 Markdown 文件。

¥To create your first blog post at https://example.com/posts/post-1/, you created a /posts/ folder and added the file post-1.md. You then added a new Markdown file to this folder every time you wanted to add a new blog post to your site.

¥Pages vs Collections

即使使用内容集合,你仍将使用 src/pages/ 文件夹来表示各个页面,例如“关于我”页面。但是,将你的博客文章移至特殊的 src/content/ 文件夹将使你能够使用更强大、性能更佳的 API 来生成你的博客文章索引并显示你的个人博客文章。

¥Even when using content collections, you will still use the src/pages/ folder for individual pages, such as your About Me page. But, moving your blog posts to the special src/content/ folder will allow you to use more powerful and performant APIs to generate your blog post index and display your individual blog posts.

同时,你将在代码编辑器中收到更好的指导和自动补齐功能,因为你将有一个 schema 来为 Astro 帮助你实现的每篇文章定义一个通用结构。在你的架构中,你可以指定何时需要 frontmatter 属性(例如描述或作者),以及每个属性必须是哪种数据类型(例如字符串或数组)。这可以更快地发现许多错误,并通过描述性错误消息准确告诉你问题是什么。

¥At the same time, you’ll receive better guidance and autocompletion in your code editor because you will have a schema to define a common structure for each post that Astro will help you enforce. In your schema, you can specify when frontmatter properties are required, such as a description or an author, and which data type each property must be, such as a string or an array. This leads to catching many mistakes sooner, with descriptive error messages telling you exactly what the problem is.

在我们的指南中阅读有关 Astro 的内容合集 的更多信息,或开始按照以下说明将基本博客从 src/pages/posts/ 转换为 src/content/posts/

¥Read more about Astro’s content collections in our guide, or get started with the instructions below to convert a basic blog from src/pages/posts/ to src/content/posts/.

¥Test your knowledge

  1. 你可能会在 src/pages/ 中保留哪种类型的页面?
  1. 以下哪个不是将博客文章移动到内容集合的好处?
  1. 内容集合使用 TypeScript…

使用内容集合扩展博客教程

Section titled 使用内容集合扩展博客教程

¥Extending the blog tutorial with content collections

以下步骤向你展示如何通过为博客文章创建内容集合来扩展构建博客教程的最终产品。

¥The steps below show you how to extend the final product of the Build a Blog tutorial by creating a content collection for the blog posts.

¥Upgrade dependencies

  1. Upgrade to the latest version of Astro, and upgrade all integrations to their latest versions by running the following commands in your terminal:

    Terminal window
    # Upgrade to Astro v4.x
    npm install astro@latest
    # Example: upgrade the blog tutorial Preact integration
    npm install @astrojs/preact@latest
  2. The blog tutorial uses the base (least strict) TypeScript setting. In order to use content collections, you must set up TypeScript for content collections either by using the strict or strictest setting, or by adding two options in tsconfig.json.

    In order to use content collections without writing TypeScript in the rest of the blog tutorial example, add the following two TypeScript configuration options to the config file:

    tsconfig.json
    {
    // Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
    "extends": "astro/tsconfigs/base",
    "compilerOptions": {
    "strictNullChecks": true,
    "allowJs": true
    }
    }

为你的博客文章创建一个集合

Section titled 为你的博客文章创建一个集合

¥Create a collection for your blog posts

  1. Create a new collection (folder) called src/content/posts/.

  2. Move all your existing blog posts (.md files) from src/pages/posts/ into this new collection.

  3. Create a src/content/config.ts file to define a schema for your postsCollection. For the existing blog tutorial code, add the following contents to the file to define all the frontmatter properties used in its blog posts:

    src/content/config.ts
    // Import utilities from `astro:content`
    import { z, defineCollection } from "astro:content";
    // Define a `type` and `schema` for each collection
    const postsCollection = defineCollection({
    type: 'content',
    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 = {
    posts: postsCollection,
    };
  4. In order for Astro to recognize your schema, quit the dev server (CTRL + C) and run the following command: npx astro sync. This will define the astro:content module for the Content Collections API. Restart the dev server to continue with the tutorial.

¥Generate pages from a collection

  1. Create a page file called src/pages/posts/[...slug].astro. Your Markdown and MDX files no longer automatically become pages using Astro’s file-based routing when they are inside a collection, so you must create a page responsible for generating each individual blog post.

  2. Add the following code to query your collection to make each blog post’s slug and page content available to each page it will generate:

    src/pages/posts/[...slug].astro
    ---
    import { getCollection } from 'astro:content';
    import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';
    export async function getStaticPaths() {
    const blogEntries = await getCollection('posts');
    return blogEntries.map(entry => ({
    params: { slug: entry.slug }, props: { entry },
    }));
    }
    const { entry } = Astro.props;
    const { Content } = await entry.render();
    ---
  3. Render your post <Content /> within the layout for Markdown pages. This allows you to specify a common layout for all of your posts.

    src/pages/posts/[...slug].astro
    ---
    import { getCollection } from 'astro:content';
    import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';
    export async function getStaticPaths() {
    const blogEntries = await getCollection('posts');
    return blogEntries.map(entry => ({
    params: { slug: entry.slug }, props: { entry },
    }));
    }
    const { entry } = Astro.props;
    const { Content } = await entry.render();
    ---
    <MarkdownPostLayout frontmatter={entry.data}>
    <Content />
    </MarkdownPostLayout>
  4. Remove the layout definition in each individual post’s frontmatter. Your content is now wrapped in a layout when rendered, and this property is no longer needed.

    src/content/posts/post-1.md
    ---
    layout: ../../layouts/MarkdownPostLayout.astro
    title: 'My First Blog Post'
    pubDate: 2022-07-01
    ...
    ---

Astro.glob() 替换为 getCollection()

Section titled 将 Astro.glob() 替换为 getCollection()

¥Replace Astro.glob() with getCollection()

  1. Anywhere you have a list of blog posts, like the tutorial’s Blog page (src/pages/blog.astro/), you will need to replace Astro.glob() with getCollection() as the way to fetch content and metadata from your Markdown files.

    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 Astro.glob("../pages/posts/*.md");
    const allPosts = await getCollection("posts");
    ---
  2. You will also need to update references to the data returned for each post. You will now find your frontmatter values on the data property of each object. Also, when using collections each post object will have a page slug, not a full 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("posts");
    ---
    <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.slug}/`} title={post.data.title} />
    ))
    }
    </ul>
    </BaseLayout>
  3. The tutorial blog project also dynamically generates a page for each tag using src/pages/tags/[tag].astro and displays a list of tags at src/pages/tags/index.astro.

    Apply the same changes as above to these two files:

    • fetch data about all your blog posts using getCollection("posts") instead of using Astro.glob()
    • access all frontmatter values using data instead of frontmatter
    • create a page URL by adding the post’s slug to the /posts/ path

    The page that generates individual tag pages now becomes:

    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("posts");
    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.slug}/`} title={post.data.title} />) }
    </ul>
    </BaseLayout>

    Try it yourself - Update the query in the Tag Index page

    Section titled Try it yourself - Update the query in the Tag Index page

    Import and use getCollection to fetch the tags used in the blog posts on src/pages/tags/index.astro, following the same steps as above.

    Show me the code.
    src/pages/tags/index.astro
    ---
    import { getCollection } from "astro:content";
    import BaseLayout from "../../layouts/BaseLayout.astro";
    const allPosts = await getCollection("posts");
    const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
    const pageTitle = "Tag Index";
    ---
    ...

更新任何 frontmatter 值以匹配你的架构

Section titled 更新任何 frontmatter 值以匹配你的架构

¥Update any frontmatter values to match your schema

  1. If necessary, update any frontmatter values throughout your project, such as in your layout, that do not match your collections schema.

    In the blog tutorial example, pubDate was a string. Now, according to the schema that defines types for the post frontmatter, pubDate will be a Date object.

    To render the date in the blog post layout, convert it to a string:

    src/layouts/MarkdownPostLayout.astro
    ...
    <BaseLayout pageTitle={frontmatter.title}>
    <p>{frontmatter.pubDate.toString().slice(0,10)}</p>
    <p><em>{frontmatter.description}</em></p>
    <p>Written by: {frontmatter.author}</p>
    <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
    ...

¥Update RSS function

  1. Lastly, the tutorial blog project includes an RSS feed. This function must also use getCollection() to return information from your blog posts. You will then generate the RSS items using the data object returned.

    src/pages/rss.xml.js
    import rss from '@astrojs/rss';
    import { pagesGlobToRssItems } from '@astrojs/rss';
    import { getCollection } from 'astro:content';
    export async function GET(context) {
    const posts = await getCollection("posts");
    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.slug}/`,
    })),
    customData: `<language>en-us</language>`,
    })
    }

有关使用内容集合的博客教程的完整示例,请参阅教程存储库的 内容馆藏分支

¥For the full example of the blog tutorial using content collections, see the Content Collections branch of the tutorial repo.

Astro 中文网 - 粤ICP备13048890号