F FisherHub Docs

02. 内容与路由

Content Collections

Content Collections 是 Astro 内置的内容管理系统,用于在 src/content/ 目录下组织和管理结构化内容。每个集合通过配置文件定义 schema,提供类型安全和自动校验。

以本文档站为例,src/content.config.ts 的配置:

// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'

const docs = defineCollection({
  loader: glob({ pattern: '**/*.md', base: 'src/content/docs' }),
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
  }),
})

export const collections = { docs }

关键配置项:

  • loader — 指定内容来源,glob 加载器可以扫描文件系统
  • schema — 用 Zod 定义 frontmatter 字段的类型,支持 z.string()z.date()z.enum()

在页面中查询内容集合:

---
import { getCollection } from 'astro:content'

// 获取 docs 集合中的所有条目
const allDocs = await getCollection('docs')

// 按 slug 排序
const sorted = allDocs.sort((a, b) =>
  a.id.localeCompare(b.id)
)
---

<h1>文档列表</h1>
<ul>
  {sorted.map(doc => (
    <li>
      <a href={`/docs/${doc.id}`}>{doc.data.title}</a>
      <p>{doc.data.description}</p>
    </li>
  ))}
</ul>

getCollection() 返回的条目包含 iddata(经过校验的 frontmatter)、body(原始内容)等属性。

Markdown / MDX

Astro 对 .md.mdx 文件提供一等支持。Markdown 文件通过 YAML frontmatter 定义元数据:

---
title: Hello World
description: 第一篇博客
pubDate: 2025-06-01
tags: [astro, guide]
layout: ../../layouts/BlogLayout.astro
---

MDX 在 Markdown 基础上允许在内容中直接使用 Astro 或框架组件:

---
title: MDX 示例
layout: ../../layouts/BlogLayout.astro
---

这是一个 MDX 文章。可以在内容中插入组件:

import Counter from '../../components/Counter.astro'

## 计数器示例

<Counter initialValue={10} />

要在项目中使用 MDX,需要安装 @astrojs/mdx 集成:

bun add @astrojs/mdx

然后在 astro.config.mjs 中添加:

import { defineConfig } from 'astro/config'
import mdx from '@astrojs/mdx'

export default defineConfig({
  integrations: [mdx()],
})

动态路由

动态路由允许根据参数生成多个页面。文件名用方括号 [param] 表示动态段。

目录结构示例:

src/pages/
└── blog/
    ├── [slug].astro        → /blog/:slug
    └── tags/[tag].astro    → /blog/tags/:tag

在页面中使用 getStaticPaths() 声明所有可能的路径:

---
// src/pages/blog/[slug].astro
import { getCollection } 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 post.render()
---

<h1>{post.data.title}</h1>
<Content />

getStaticPaths() 返回一个数组,每项包含 params(URL 参数)和 props(传入组件的属性)。Astro 在构建时会根据这个数组生成所有页面。

分页

Astro 内置分页功能,通过 paginate() 函数实现:

---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content'
import Pagination from '../../components/Pagination.astro'

export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('blog')
  const sorted = posts.sort((a, b) =>
    b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
  )

  return paginate(sorted, { pageSize: 10 })
}

const { page } = Astro.props
---

<h1>博客列表 - 第 {page.currentPage} 页</h1>
<ul>
  {page.data.map(post => (
    <li>
      <a href={`/blog/${post.id}`}>{post.data.title}</a>
      <time>{post.data.pubDate.toLocaleDateString('zh-CN')}</time>
    </li>
  ))}
</ul>
<Pagination page={page} />

page 对象包含以下属性:

属性说明
page.data当前页的条目数组
page.currentPage当前页码
page.lastPage总页数
page.url.next下一页 URL(无下一页时为 undefined
page.url.prev上一页 URL

RSS 生成

使用 @astrojs/rss 包可以快速生成 RSS Feed:

bun add @astrojs/rss

src/pages/rss.xml.ts 中创建 RSS 端点:

import rss from '@astrojs/rss'
import { getCollection } from 'astro:content'

export async function GET(context) {
  const posts = await getCollection('blog')
  const sorted = posts.sort((a, b) =>
    b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
  )

  return rss({
    title: '我的博客',
    description: '技术文章与心得分享',
    site: context.site,
    items: sorted.map(post => ({
      title: post.data.title,
      pubDate: post.data.pubDate,
      description: post.data.description,
      link: `/blog/${post.id}/`,
    })),
  })
}

访问 /rss.xml 即可获取 RSS Feed。需要确保 astro.config.mjs 中配置了 site 字段。