定义集合 #

一、集合配置文件 #

1.1 配置文件位置 #

内容集合的配置文件位于 src/content/config.ts

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

const blog = defineCollection({
  // 集合配置
});

export const collections = {
  blog,
};

1.2 集合类型 #

类型 说明 适用场景
content 内容文件(Markdown/MDX) 博客文章、文档
data 数据文件(JSON/YAML) 作者信息、配置
typescript
import { defineCollection } from 'astro:content';

const blog = defineCollection({
  type: 'content',  // Markdown/MDX 文件
});

const authors = defineCollection({
  type: 'data',     // JSON/YAML 文件
});

export const collections = { blog, authors };

二、Schema 定义 #

2.1 基本类型 #

typescript
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  schema: z.object({
    // 字符串
    title: z.string(),
    description: z.string(),
    
    // 数字
    order: z.number(),
    rating: z.number().min(1).max(5),
    
    // 布尔值
    draft: z.boolean(),
    featured: z.boolean(),
    
    // 日期
    date: z.date(),
    updated: z.date().optional(),
    
    // 数组
    tags: z.array(z.string()),
    
    // 对象
    author: z.object({
      name: z.string(),
      email: z.string().email(),
    }),
  }),
});

2.2 字符串验证 #

typescript
const blog = defineCollection({
  schema: z.object({
    // 基本字符串
    title: z.string(),
    
    // 非空字符串
    title: z.string().min(1),
    
    // 长度限制
    title: z.string().min(1).max(100),
    description: z.string().min(10).max(200),
    
    // 正则验证
    slug: z.string().regex(/^[a-z0-9-]+$/),
    
    // 枚举值
    status: z.enum(['draft', 'published', 'archived']),
    
    // 邮箱
    email: z.string().email(),
    
    // URL
    website: z.string().url(),
    
    // 非空字符串(排除空格)
    title: z.string().trim().min(1),
  }),
});

2.3 数字验证 #

typescript
const product = defineCollection({
  schema: z.object({
    // 基本数字
    price: z.number(),
    
    // 整数
    quantity: z.number().int(),
    
    // 正数
    price: z.number().positive(),
    
    // 范围限制
    rating: z.number().min(0).max(5),
    discount: z.number().min(0).max(100),
    
    // 默认值
    quantity: z.number().default(0),
  }),
});

2.4 日期处理 #

typescript
const blog = defineCollection({
  schema: z.object({
    // Date 对象
    date: z.date(),
    
    // 从字符串转换
    date: z.coerce.date(),
    
    // 从字符串或数字转换
    date: z.coerce.date(),
    
    // 可选日期
    updated: z.coerce.date().optional(),
  }),
});

2.5 数组验证 #

typescript
const blog = defineCollection({
  schema: z.object({
    // 字符串数组
    tags: z.array(z.string()),
    
    // 非空数组
    tags: z.array(z.string()).min(1),
    
    // 数组长度限制
    tags: z.array(z.string()).min(1).max(10),
    
    // 数字数组
    ratings: z.array(z.number()),
    
    // 对象数组
    authors: z.array(z.object({
      name: z.string(),
      role: z.string(),
    })),
    
    // 默认值
    tags: z.array(z.string()).default([]),
  }),
});

三、可选和默认值 #

3.1 可选字段 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    
    // 可选字段
    subtitle: z.string().optional(),
    image: z.string().optional(),
    author: z.string().optional(),
    
    // 可空字段
    description: z.string().nullable(),
  }),
});

3.2 默认值 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    
    // 布尔默认值
    draft: z.boolean().default(false),
    featured: z.boolean().default(false),
    toc: z.boolean().default(true),
    
    // 字符串默认值
    author: z.string().default('匿名'),
    layout: z.string().default('blog'),
    
    // 数组默认值
    tags: z.array(z.string()).default([]),
    categories: z.array(z.string()).default([]),
    
    // 对象默认值
    seo: z.object({
      title: z.string().optional(),
      description: z.string().optional(),
    }).default({}),
  }),
});

四、高级验证 #

4.1 条件验证 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    image: z.string().optional(),
    imageAlt: z.string().optional(),
  }).refine(
    data => !data.image || data.imageAlt,
    { message: '有图片时必须提供 imageAlt' }
  ),
});

4.2 自定义验证 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    slug: z.string(),
    date: z.coerce.date(),
  }).refine(
    data => data.slug === data.title.toLowerCase().replace(/\s+/g, '-'),
    { message: 'slug 必须是标题的小写连字符形式' }
  ),
});

4.3 转换数据 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    
    // 转换为大写
    category: z.string().transform(val => val.toUpperCase()),
    
    // 转换并验证
    tags: z.string().transform(val => val.split(',').map(t => t.trim())),
    
    // 复杂转换
    date: z.string().transform(val => new Date(val)),
  }),
});

4.4 联合类型 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    
    // 联合类型
    status: z.union([
      z.literal('draft'),
      z.literal('published'),
      z.literal('archived'),
    ]),
    
    // 简写形式
    status: z.enum(['draft', 'published', 'archived']),
    
    // 可辨识联合
    content: z.discriminatedUnion('type', [
      z.object({ type: z.literal('text'), text: z.string() }),
      z.object({ type: z.literal('image'), src: z.string(), alt: z.string() }),
    ]),
  }),
});

五、图片处理 #

5.1 图片字段 #

typescript
const blog = defineCollection({
  schema: ({ image }) => z.object({
    title: z.string(),
    
    // 图片字段
    cover: image(),
    
    // 图片带约束
    cover: image().refine(img => img.width >= 1200, {
      message: '封面图片宽度至少 1200px',
    }),
    
    // 可选图片
    cover: image().optional(),
  }),
});

5.2 使用图片 #

astro
---
import { getEntry } from 'astro:content';
import { Image } from 'astro:assets';

const post = await getEntry('blog', 'hello-world');
---

<article>
  {post.data.cover && (
    <Image 
      src={post.data.cover} 
      alt={post.data.title}
      width={800}
    />
  )}
</article>

六、关系定义 #

6.1 引用其他集合 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.coerce.date(),
    
    // 引用作者集合
    author: z.string(),
  }),
});

const authors = defineCollection({
  type: 'data',
  schema: z.object({
    name: z.string(),
    email: z.string().email(),
    bio: z.string().optional(),
  }),
});

export const collections = { blog, authors };

6.2 关联查询 #

astro
---
import { getEntry, getCollection } from 'astro:content';

const post = await getEntry('blog', 'hello-world');
const author = await getEntry('authors', post.data.author);
---

<article>
  <h1>{post.data.title}</h1>
  <p>作者: {author.data.name}</p>
</article>

七、完整示例 #

7.1 博客集合 #

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

const blog = defineCollection({
  type: 'content',
  schema: ({ image }) => z.object({
    title: z.string().min(1).max(100),
    description: z.string().min(10).max(200),
    date: z.coerce.date(),
    updated: z.coerce.date().optional(),
    author: z.string().default('anonymous'),
    cover: image().optional(),
    coverAlt: z.string().optional(),
    tags: z.array(z.string()).default([]),
    categories: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    featured: z.boolean().default(false),
    toc: z.boolean().default(true),
    comments: z.boolean().default(true),
  }).refine(
    data => !data.cover || data.coverAlt,
    { message: '有封面图时必须提供 coverAlt' }
  ),
});

const authors = defineCollection({
  type: 'data',
  schema: z.object({
    name: z.string(),
    avatar: z.string().optional(),
    bio: z.string().optional(),
    website: z.string().url().optional(),
    twitter: z.string().optional(),
    github: z.string().optional(),
  }),
});

const docs = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    order: z.number().int().default(0),
    section: z.string().optional(),
    prev: z.string().optional(),
    next: z.string().optional(),
  }),
});

export const collections = {
  blog,
  authors,
  docs,
};

7.2 内容文件示例 #

markdown
---
title: Astro
description: 介绍如何定义 Astro 内容集合,包括 Schema 配置、类型定义和高级验证。
date: 2024-01-15
author: zhangsan
cover: ./images/astro-guide.png
coverAlt: Astro 框架 Logo
tags:
  - Astro
  - 前端
  - 静态站点
categories:
  - 教程
featured: true
toc: true
---

# Astro 完全指南

Astro 是一款现代化的静态站点生成器...

八、最佳实践 #

8.1 Schema 组织 #

typescript
// src/content/schemas/common.ts
import { z } from 'astro:content';

export const seoSchema = z.object({
  title: z.string().optional(),
  description: z.string().optional(),
  image: z.string().optional(),
});

export const authorSchema = z.object({
  name: z.string(),
  email: z.string().email().optional(),
  url: z.string().url().optional(),
});
typescript
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
import { seoSchema, authorSchema } from './schemas/common';

const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    seo: seoSchema,
    author: authorSchema,
  }),
});

8.2 错误提示 #

typescript
const blog = defineCollection({
  schema: z.object({
    title: z.string({
      required_error: '标题是必填项',
      invalid_type_error: '标题必须是字符串',
    }).min(1, '标题不能为空'),
    
    date: z.coerce.date({
      required_error: '日期是必填项',
      invalid_type_error: '日期格式无效',
    }),
  }),
});

九、总结 #

定义集合核心要点:

text
┌─────────────────────────────────────────────────────┐
│                 定义集合要点                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📄 配置文件   src/content/config.ts               │
│                                                     │
│  🔧 集合类型   content / data                       │
│                                                     │
│  ✅ Schema     使用 Zod 定义验证规则                │
│                                                     │
│  📝 字段类型   string/number/boolean/date/array    │
│                                                     │
│  🔗 关系       引用其他集合                         │
│                                                     │
│  🖼️ 图片      image() 字段类型                     │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 查询数据,掌握内容查询 API!

最后更新:2026-03-28