Content Collections
Relevant source files
The following files were used as context for generating this wiki page:Content collections are the primary method for managing and organizing content within the Charlotte project, leveraging Astro's content collections feature. This system provides a structured way to handle Markdown-based content, such as blog posts, by defining schemas, validating frontmatter, and providing a typed API for querying content.
The project defines two main collections: posts for blog articles and spec for special-purpose pages. These collections are the foundation for generating post listings, archive pages, RSS/Atom feeds, and individual post pages. The entire content lifecycle, from creation and validation to querying and display, is managed through this system.
Collection Definitions
The content collections are defined in src/content/config.ts using Astro's defineCollection utility and Zod for schema validation.
// src/content/config.ts:1-26
import { defineCollection, z } from "astro:content";
const postsCollection = defineCollection({
schema: z.object({
title: z.string(),
published: z.date(),
updated: z.date().optional(),
draft: z.boolean().optional().default(false),
description: z.string().optional().default(""),
image: z.string().optional().default(""),
tags: z.array(z.string()).optional().default([]),
category: z.string().optional().nullable().default(""),
lang: z.string().optional().default(""),
isPinned: z.boolean().optional().default(false),
/* For internal use */
prevTitle: z.string().default(""),
prevSlug: z.string().default(""),
nextTitle: z.string().default(""),
nextSlug: z.string().default(""),
}),
});
const specCollection = defineCollection({
schema: z.object({}),
});
export const collections = {
posts: postsCollection,
spec: specCollection,
};
Sources: src/content/config.ts:1-26
posts Collection
This is the main collection for all blog posts. It enforces a strict schema on the frontmatter of each Markdown file located in src/content/posts/.
Schema
The schema for the posts collection ensures that all posts have consistent and valid metadata. The following diagram and table detail the fields defined in the schema.
This ER diagram illustrates the posts collection schema.
Sources: src/content/config.ts:4-20
| Field | Type | Description | Default |
|---|---|---|---|
title | string | The title of the post. | (required) |
published | date | The publication date of the post. | (required) |
updated | date | The last updated date of the post. | optional |
draft | boolean | If true, the post will not be displayed in production builds. | false |
description | string | A short description or summary of the post. | "" |
image | string | Path to the post's cover image. | "" |
tags | string[] | An array of tags associated with the post. | [] |
category | string | The category of the post. | "" |
lang | string | The language of the post. | "" |
isPinned | boolean | Internal flag set to true if the post is pinned. | false |
prevTitle | string | (Internal) Title of the previous post. | "" |
prevSlug | string | (Internal) Slug of the previous post. | "" |
nextTitle | string | (Internal) Title of the next post. | "" |
nextSlug | string | (Internal) Slug of the next post. | "" |
| Sources: src/content/config.ts:4-20, src/content/posts/2018-10-30/Charlotte-Markdown.md:2-11 |
The internal fields (prevTitle, prevSlug, etc.) are populated dynamically by the getSortedPosts utility function to enable navigation between posts.
Sources: src/utils/content-utils.ts:80-87
spec Collection
The spec collection is defined with an empty schema. It is used for special, one-off content pages that do not follow the post structure, such as the projects page.
Sources: src/content/config.ts:21-23, src/content/spec/projects.md
Content Management
File Structure and Creation
All blog posts are stored as Markdown files under the src/content/posts/ directory. A subdirectory can be created to group a post's Markdown file and its associated assets, like images.
src/content/posts/
├── post-1.md
└── post-2/
├── cover.png
└── index.md
Sources: src/content/posts/2018-10-30/Charlotte-Markdown.md:29-34
A Node.js script is provided to facilitate the creation of new posts. Running pnpm new-post <filename> will generate a new Markdown file in src/content/posts/ with a pre-filled frontmatter template.
// scripts/new-post.js:41-51
const content = `---
title: ${args[0]}
published: ${getDate()}
description: ''
image: ''
tags: []
category: ''
draft: false
lang: ''
---
`;
Sources: scripts/new-post.js:1-53, package.json:9
Frontmatter Configuration
For a better development experience with the Front Matter VS Code extension, a frontmatter.json file is configured. It defines the content types and fields, providing autocompletion and validation within the editor.
This diagram shows how frontmatter.json maps to the post content type.
Sources: frontmatter.json:1-74
Content Consumption and Processing
The src/utils/content-utils.ts file contains the core logic for querying, filtering, sorting, and processing content from the collections.
Fetching and Sorting Posts
The getSortedPosts function is the primary utility for retrieving all posts. It sorts them by publication date in descending order and applies pinning logic.
The following flowchart illustrates the process of getSortedPosts.
Pinned Posts
Posts can be "pinned" to the top of lists. The slugs of the posts to be pinned are defined in the pinnedPost array in src/config.ts. The applyPinnedOrdering function in content-utils.ts reads this configuration and prepends the specified posts to the sorted list.
// src/config.ts:50
export const siteConfig: SiteConfig = {
// ...
pinnedPost: ["2025-09-29/Unveiling"], // 置顶文章的 slug 路径
// ...
};
Sources: src/config.ts:50, src/utils/content-utils.ts:12-20
Generating Feeds (RSS & Atom)
The content collections are used to generate RSS and Atom feeds for the blog. The endpoints rss.xml.ts and atom.xml.ts fetch the sorted posts, process their content, and generate the final XML output.
This sequence diagram shows the RSS feed generation process.
During feed generation, relative URLs for images and links in the post body are converted to absolute URLs to ensure they are accessible from feed readers. The HTML is then sanitized to remove potentially harmful tags and attributes before being included in the feed.
Sources: src/pages/rss.xml.ts:44-51, src/pages/rss.xml.ts:54-57