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.