This episode covers content management in Astro: writing with Markdown and MDX, content collections with validated schemas, frontmatter and metadata, and how to render dynamic content on static pages.

Astro is a framework for content-driven sites, and episode 5 sits right at its heart: how you write, manage, and display content. This is where Astro's power is felt the most — Markdown content becomes fast pages with strict validation.
You will learn three big things: writing content with Markdown and MDX, defining content collections with schemas validated at build time, and structuring frontmatter as metadata for every piece of content.
By the end of the episode, you can build a blog or documentation site with Astro — anyone writes the content, and the pages assemble automatically.
.md files in src/pages automatically become pages. Create src/pages/blog/satu.md:
---
title: "Artikel Pertama"
description: "Contoh artikel markdown"
published: "2026-08-10"
---
## Pendahuluan
Ini adalah konten yang ditulis dengan **Markdown**. Astro merender
file ini menjadi halaman HTML statis saat build.
- Daftar item satu
- Daftar item duaThe frontmatter between --- becomes the page metadata, and the file body becomes the content. One thing to note: writing published: "2026-08-10" in the frontmatter is a metadata declaration — there is no validation at all until we define a collection.
If your content needs interactive components, use MDX. Enable the integration first:
npx astro add mdxAfter that, .mdx files can use components directly inside the content. The key difference: Markdown is structured text only, while MDX allows JSX and components inside the content.
Content collections turn Markdown from "a pile of files" into "validated data". Collection definitions live in src/content.config.ts (Astro 5) or src/content/config.ts (earlier versions):
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
published: z.date(),
tags: z.array(z.string()),
}),
});
export const collections = { blog };The schema above uses z.object from Zod. At build time, Astro validates every frontmatter against the schema — if title is missing or published is not a date, the build fails with a clear message.
Content files for the blog collection live in src/content/blog. You can sort content with order() or mark drafts with .draft(). Complete metadata such as published and tags lets pages do filtering, sorting, and grouping.
To build a blog listing page, query the collection with getCollection:
---
import { getCollection } from "astro:content";
const posts = (await getCollection("blog")).sort(
(a, b) => b.data.published.valueOf() - a.data.published.valueOf(),
);
---
<h1>Daftar Artikel</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.id}`}>
{post.data.title} — {post.data.published.toISOString().slice(0, 10)}
</a>
</li>
))}
</ul>getCollection("blog") returns all valid entries, and each post.data holds metadata validated by the schema. This list is rendered into static HTML — visitors do not wait for any JavaScript.
For per-slug article pages, combine the collection with getStaticPaths:
---
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 render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>render(post) produces a ready-to-render content component, and the whole page is built once at build time.
Warning
Never drop Markdown files randomly into src/content. Only files matching a collection defined in content.config.ts will be processed — anything else triggers a build error.
The biggest benefit of content collections is consistency. Because every field is validated, contributors cannot forget to write description or use a date in the wrong format. For large amounts of content, use z.enum for categories and z.url for links so values are always valid.
Because collection data is available as plain data structures, you can count articles per tag, build an index, or generate "related posts" lists without extra queries. All the computation runs at build time, so pages stay static and fast.
Episode 5 unlocks Astro's content management power: writing with Markdown and MDX, defining content collections with Zod schemas, using frontmatter as structured metadata, and rendering dynamic content on static pages through getCollection and render.
The key takeaways:
.md and .mdx files in src/pages automatically become pages.src/content.config.ts.getCollection("nama") fetches all valid entries.getStaticPaths builds detail pages for every entry.render(post) turns an entry into a content component.In the next episode 6, we will cover data fetching and server-side rendering: fetching data at build time, integrating external APIs, SSR mode with adapters, and progressive and partial hydration. Your content will start connecting to the outside world.