Published Feb 15, 2024
Building a Static Blog with Astro and Markdown
Astro's content collections give you type-safe frontmatter, automatic slug generation, and a build system that just works — without reaching for a database.
Astro is unusually good at exactly one thing: generating static pages from content. It does not try to be a full-stack framework. It makes no assumptions about your backend. It just takes your files and turns them into HTML.
For a personal blog, that is exactly enough.
Content collections
The key primitive is a content collection — a directory of files with a shared schema. Define the schema once in src/content.config.ts, and every post in src/content/posts/ gets validated against it at build time.
import { glob } from "astro/loaders";
import { defineCollection } from "astro:content";
import { z } from "astro/zod";
const posts = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/posts" }),
schema: z.object({
title: z.string().min(1).max(160),
date: z.coerce.date(),
excerpt: z.string().max(280).optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { posts };
If a post is missing a required field — or the date is malformed — the build fails loudly. No silent data corruption, no runtime surprises.
Generating routes
Each post becomes a static route via getStaticPaths:
export async function getStaticPaths() {
const posts = await getCollection("posts", ({ data }) => !data.draft);
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
}));
}
The slug comes from the filename. building-with-astro.md becomes /posts/building-with-astro. No configuration, no explicit slug field in frontmatter.
Rendering Markdown
Inside the page, render() turns the Markdown body into a ready-to-use component:
const { Content } = await render(post);
Drop <Content /> anywhere in the template and the post body renders, including syntax-highlighted code blocks via rehype-highlight.
What you get for free
Every post is a file. Every deploy is a push. The build is a single command.
No API to call, no database to query, no credentials to rotate. The entire site fits in a git repository. Rolling back means git revert.
The tradeoff is that you can not edit from a browser. For a personal blog where the author is also the developer, that is not a tradeoff at all.