Type-safe frontmatter with Zod
Every post on this site declares frontmatter — a title, a date, a description, maybe some tags. The problem with frontmatter is that it is just untyped data sitting at the top of a file, easy to get subtly wrong.
One schema, two jobs
A single Zod schema does double duty: it validates the
frontmatter at build time, and it is the source of the TypeScript type via
z.infer, so the runtime contract and the compile-time type can never drift
apart.
import { z } from 'zod'
export const frontmatterSchema = z.object({
title: z.string().min(1),
date: z.coerce.date(),
description: z.string().min(1),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
})
export type Frontmatter = z.infer<typeof frontmatterSchema>Tip
Using z.coerce.date() means a quoted string and a YAML date both parse the
same way — and an unparseable value fails validation, which fails the build.
If a post is missing a required field or has a malformed date, the build stops and tells you which post. A broken post never makes it to production.