1. Pengenalan Content Collections
Content Collections adalah fitur Astro untuk mengelola konten lokal (Markdown, MDX, data files) secara terstruktur. Collections menyediakan validasi schema, type-safe queries, dan API yang konsisten untuk mengakses konten Anda.
Bayangkan Anda punya ratusan file Markdown untuk blog, dokumentasi, atau portofolio. Tanpa collections, Anda harus membaca file secara manual, parse frontmatter, dan mengelola datanya sendiri. Dengan Content Collections, Astro melakukan semua itu — plus validasi dan type safety.
Mengapa Content Collections?
| Fitur | Tanpa Collections | Dengan Collections |
|---|---|---|
| Schema Validation | Manual, raw frontmatter | Otomatis dengan Zod |
| Type Safety | String everywhere | TypeScript types ter-generate |
| Query | import.meta.glob + filter | getCollection() API |
| Error | Silent failure | Build error jika schema invalid |
| Sorting/Filtering | Manual array manipulation | Built-in query functions |
Content Collections adalah alasan utama mengapa banyak developer memilih Astro untuk website konten-heavy seperti blog, dokumentasi, dan marketing sites. Fitur ini menghilangkan sakit kepala dalam mengelola konten Markdown.
2. Project Setup
# Membuat project Astro baru npm create astro@latest my-blog cd my-blog # Pilih template "Blog" saat setup # Atau tambahkan collections manual ke project yang ada # Jalankan development server npm run dev # Server berjalan di http://localhost:4321 # Struktur folder Content Collections: # my-blog/ # ├── src/ # │ ├── content/ ← Content Collections # │ │ ├── config.ts ← Schema definitions # │ │ ├── blog/ ← Blog collection # │ │ │ ├── post-1.md # │ │ │ ├── post-2.mdx # │ │ │ └── post-3.md # │ │ └── docs/ ← Docs collection # │ │ ├── getting-started.md # │ │ └── api-reference.md # │ └── pages/ # │ ├── index.astro # │ └── blog/ # │ ├── index.astro # │ └── [...slug].astro # └── astro.config.mjs
3. Schema Definition
Schema mendefinisikan struktur data frontmatter untuk setiap collection. Astro menggunakan Zod untuk validasi schema dan TypeScript type inference.
// src/content/config.ts
import { defineCollection, z } from "astro:content";
// Definisi schema untuk collection "blog"
const blogCollection = defineCollection({
type: "content", // "content" untuk Markdown/MDX, "data" untuk JSON/YAML
schema: z.object({
title: z.string().min(5).max(100),
description: z.string().min(20).max(300),
pubDate: z.coerce.date(), // Auto-parse string ke Date
updatedDate: z.coerce.date().optional(),
author: z.string().default("Admin"),
category: z.enum([
"tutorial",
"berita",
"tips",
"opini",
]),
tags: z.array(z.string()).min(1).max(10),
image: z.object({
src: z.string(),
alt: z.string(),
}).optional(),
draft: z.boolean().default(false), // Jangan publish jika true
featured: z.boolean().default(false),
readingTime: z.number().min(1).optional(),
}),
});
// Definisi schema untuk collection "docs"
const docsCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string(),
section: z.string(),
order: z.number().default(0), // Urutan dalam sidebar
badge: z.enum(["new", "updated", ""]).optional(),
}),
});
// Definisi schema untuk collection "authors" (data collection)
const authorsCollection = defineCollection({
type: "data",
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string().url(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
website: z.string().url().optional(),
}).optional(),
}),
});
// Export semua collections
export const collections = {
blog: blogCollection,
docs: docsCollection,
authors: authorsCollection,
};
--- title: "Tutorial Astro Content Collections" description: "Pelajari cara mengelola konten dengan Astro Content Collections" pubDate: 2026-06-29 author: "Budi Santoso" category: "tutorial" tags: ["astro", "web-development", "tutorial"] image: src: "/images/blog/astro-collections.png" alt: "Astro Content Collections" draft: false featured: true readingTime: 8 --- ## Apa itu Content Collections? Content Collections adalah fitur terbaru dari Astro yang memudahkan pengelolaan konten Markdown. Dengan collections, Anda bisa mendefinisikan schema untuk frontmatter dan mendapatkan type safety secara otomatis. ### Keunggulan - **Validasi otomatis** — error di-build time jika frontmatter invalid - **Type safety** — TypeScript tahu tipe data setiap field - **Query yang powerful** — filter, sort, dan paginate dengan mudah
4. Collection Queries
Astro menyediakan API getCollection() dan getEntry() untuk mengakses konten secara type-safe.
// src/pages/blog/index.astro
---
import { getCollection } from "astro:content";
// Ambil semua post yang bukan draft
const posts = await getCollection("blog", ({ data }) => {
return !data.draft; // Filter: hanya yang draft=false
});
// Sort berdasarkan tanggal terbaru
const sortedPosts = posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
// Group berdasarkan category
const featured = sortedPosts.filter((p) => p.data.featured);
const recent = sortedPosts.slice(0, 10);
---
<h1>Blog</h1>
<section>
<h2>Featured</h2>
{featured.map((post) => (
<article>
<h3><a href={`/blog/${post.slug}`}>{post.data.title}</a></h3>
<p>{post.data.description}</p>
<time>{post.data.pubDate.toLocaleDateString("id-ID")}</time>
</article>
))}
</section>
<section>
<h2>Terbaru</h2>
{recent.map((post) => (
<article>
<h3><a href={`/blog/${post.slug}`}>{post.data.title}</a></h3>
<span>{post.data.category}</span>
</article>
))}
</section>
// src/pages/blog/[...slug].astro
---
import { getCollection, getEntry } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";
// Generate static paths untuk semua post
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
// Render konten Markdown
const { Content, headings } = await post.render();
// Ambil data author dari collection terpisah
const author = await getEntry("authors", post.data.author);
---
<BlogPost title={post.data.title} description={post.data.description}>
<article>
<header>
<h1>{post.data.title}</h1>
<p>Oleh: {author?.data.name ?? "Admin"}</p>
<time>{post.data.pubDate.toLocaleDateString("id-ID")}</time>
<div class="tags">
{post.data.tags.map((tag) => <span class="tag">{tag}</span>)}
</div>
</header>
{/* Table of Contents */}
<nav class="toc">
<h2>Daftar Isi</h2>
<ul>
{headings.map((h) => (
<li style={`padding-left: ${h.depth - 1}rem`}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))}
</ul>
</nav>
{/* Konten Markdown yang di-render */}
<div class="prose">
<Content />
</div>
</article>
</BlogPost>
5. Dynamic Routes
// src/pages/blog/category/[category].astro
---
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
// Ambil semua unique categories
const categories = [...new Set(posts.map((p) => p.data.category))];
return categories.map((category) => ({
params: { category },
props: {
category,
posts: posts
.filter((p) => p.data.category === category)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()),
},
}));
}
const { category, posts } = Astro.props;
---
<h1>Kategori: {category}</h1>
{posts.map((post) => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.description}</p>
</article>
))}
// src/pages/blog/tag/[tag].astro
---
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
// Kumpulkan semua tag unik
const allTags = posts.flatMap((p) => p.data.tags);
const uniqueTags = [...new Set(allTags)];
return uniqueTags.map((tag) => ({
params: { tag },
props: {
tag,
posts: posts.filter((p) => p.data.tags.includes(tag)),
},
}));
}
const { tag, posts } = Astro.props;
---
<h1>Tag: #{tag}</h1>
<p>{posts.length} artikel ditemukan</p>
{posts.map((post) => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
</article>
))}
6. MDX Integration
Astro mendukung MDX (Markdown + JSX) yang memungkinkan Anda menggunakan komponen Astro/React langsung di dalam file Markdown.
# Install MDX integration npx astro add mdx # Atau manual: npm install @astrojs/mdx
---
title: "Tutorial dengan Komponen Interaktif"
description: "Contoh MDX dengan komponen"
pubDate: 2026-06-29
category: "tutorial"
tags: ["astro", "mdx"]
---
import Callout from "../../components/Callout.astro";
import CodeSandbox from "../../components/CodeSandbox.astro";
import Counter from "../../components/Counter.tsx";
## Pengenalan MDX
MDX memungkinkan Anda menggunakan komponen Astro langsung di Markdown!
<Callout type="info">
Ini adalah komponen Callout yang di-import dari Astro.
Bisa digunakan langsung di dalam MDX!
</Callout>
### Contoh Interaktif
<Counter client:visible initialCount={0} />
<CodeSandbox id="abc123" />
## Lanjutkan dengan konten Markdown biasa...
Paragraf biasa dengan **bold**, *italic*, dan `code`.
7. RSS Feed Generation
# Install RSS integration npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import type { APIContext } from "astro";
export async function GET(context: APIContext) {
const posts = await getCollection("blog", ({ data }) => !data.draft);
// Sort berdasarkan tanggal terbaru
const sortedPosts = posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
return rss({
title: "Blog BeebaneLabs",
description: "Tutorial teknologi dan IT terbaru",
site: context.site!,
items: sortedPosts.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/blog/${post.slug}/`,
categories: [post.data.category, ...post.data.tags],
author: post.data.author,
})),
customData: "<language>id-ID</language>",
stylesheet: "/rss/styles.xsl",
});
}
// astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
export default defineConfig({
site: "https://beebanelabs.pages.dev",
integrations: [mdx(), sitemap()],
});
8. Teknik Lanjutan
Custom Renderers & Remark Plugins
// astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import remarkToc from "remark-toc";
import remarkGfm from "remark-gfm";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
export default defineConfig({
markdown: {
remarkPlugins: [
remarkGfm, // GitHub Flavored Markdown
[remarkToc, { heading: "daftar isi" }], // Auto-generate TOC
],
rehypePlugins: [
rehypeSlug, // Tambah ID ke headings
rehypeAutolinkHeadings, // Auto-link headings
],
shikiConfig: {
theme: "dracula", // Syntax highlighting theme
langs: ["javascript", "typescript", "python", "html", "css"],
},
},
integrations: [mdx()],
});
Data Layer & External APIs
// src/content/config.ts (Astro 5+ Content Layer)
import { defineCollection, z } from "astro:content";
import { glob, file } from "astro/loaders";
// Local Markdown files
const blog = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()),
}),
});
// JSON data files
const authors = defineCollection({
loader: file("./src/data/authors.json"),
schema: z.object({
id: z.string(),
name: z.string(),
bio: z.string(),
}),
});
// External API (fetch data dari CMS/API eksternal)
const products = defineCollection({
loader: async () => {
const response = await fetch("https://api.example.com/products");
const data = await response.json();
return data.map((item: any) => ({
id: item.id.toString(),
...item,
}));
},
schema: z.object({
id: z.string(),
name: z.string(),
price: z.number(),
}),
});
export const collections = { blog, authors, products };
9. Best Practices
- Selalu definisikan schema — validasi di build time mencegah error di production
- Gunakan coerce —
z.coerce.date()otomatis parse string ke Date - Draft system — gunakan field
draft: trueuntuk post yang belum siap - Image optimization — gunakan
astro:assetsuntuk gambar di content - MDX untuk interaktivitas — gunakan MDX saat butuh komponen di konten
- Filter di query — lakukan filtering langsung di
getCollection() - RSS selalu tersedia — buat rss.xml untuk setiap blog
- Sitemap — gunakan
@astrojs/sitemapuntuk SEO - Type export — export type dari schema untuk digunakan di komponen
- Content Layer API — gunakan untuk data eksternal (CMS, API)
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial Astro Content Collections, jawablah 5 pertanyaan berikut: