ข้ามไปยังเนื้อหา

Content Collections

ใน Next.js คุณมักใช้ contentlayer หรือ fs + gray-matter เพื่อโหลดไฟล์ MDX พร้อม type safety Astro มี content collections built-in: วางไฟล์ MD/MDX ใน src/content/<collection>/ กำหนด schema ด้วย zod ใน src/content.config.ts แล้วใช้ getCollection('blog') ที่ไหนก็ได้ในโปรเจกต์ — มี type safety เต็มรูปแบบ ไม่ต้องใช้ library ของ third-party

ต้องใช้โปรเจกต์ Astro เต็มรูปแบบ ใช้ npm create astro@latest เพื่อสร้างโปรเจกต์

React
// contentlayer.config.ts (Next.js)
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
export const Post = defineDocumentType(() => ({
name: 'Post',
filePathPattern: 'blog/**/*.mdx',
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
date: { type: 'date', required: true },
tags: { type: 'list', of: { type: 'string' } },
},
}));
export default makeSource({ contentDirPath: 'content', documentTypes: [Post] });
// In a page:
// import { allPosts } from 'contentlayer/generated';
Astro
// src/content.config.ts (Astro)
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };
// In a page:
// import { getCollection } from 'astro:content';
// const posts = await getCollection('blog');

Schema อยู่ใน src/content.config.ts — Astro ตรวจสอบทุกไฟล์ใน collection ตอน build time และแสดง type errors ใน editor ของคุณ

สองรายละเอียดที่เปลี่ยนใน Astro 6/7 และ tutorial เก่า ๆ มักเขียนผิด: ทุก collection ต้องประกาศ loader (glob() สำหรับโฟลเดอร์ไฟล์, file() สำหรับไฟล์ JSON/YAML ไฟล์เดียว — ทั้งคู่มาจาก astro/loaders) แทน flag type: 'content' | 'data' แบบเดิม และ z ให้ import จาก astro/zod (Zod 4) แทน astro:content ส่วน entry ระบุด้วย entry.id (มาจากชื่อไฟล์) ส่วน entry.slug แบบเก่าถูกถอดออกแล้ว

src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
// glob() loads a folder of MD/MDX; file() loads one JSON/YAML file
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().optional(),
date: z.coerce.date(),
draft: z.boolean().default(false),
tags: z.array(z.string()).default([]),
}),
});
export const collections = { blog };
src/pages/blog/index.astro
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);
const sorted = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<html lang="en">
<head><meta charset="utf-8" /><title>Blog</title></head>
<body>
<h1>Blog</h1>
<ul>
{sorted.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
</body>
</html>

argument ที่สองของ getCollection คือ optional filter function — ที่นี่ใช้กรอง drafts ออก post.data มี type จาก zod schema เต็มรูปแบบ

คุณกำหนด schema สำหรับ content collection ที่ไหน?
ดึงข้อมูลทุก entry จาก collection ชื่อ 'blog' ได้อย่างไร?
ข้อดีหลักของ content collections เทียบกับ `fs` + `gray-matter` คืออะไร?