Content Model Guide - Schema Design Patterns
Overview
Section titled “Overview”This guide covers best practices for designing content schemas in Astro using Content Collections. Well-designed schemas ensure type safety, content consistency, and excellent developer experience.
Core Principles
Section titled “Core Principles”- Type Safety First: Leverage Zod for runtime validation
- Future-Proof: Design for content evolution
- DRY: Reuse schema components
- Validation: Catch errors at build time
- Flexibility: Support various content needs
Schema Design Patterns
Section titled “Schema Design Patterns”1. Base Schema Pattern
Section titled “1. Base Schema Pattern”Create reusable base schemas for common fields:
// src/content.config.ts — Content Layer config lives at the src rootimport { defineCollection } from 'astro:content';import { glob } from 'astro/loaders';import { z } from 'astro/zod';
// Reusable schema componentsconst seoSchema = z.object({ metaTitle: z.string().max(60).optional(), metaDescription: z.string().max(160).optional(), ogImage: z.string().optional(), noindex: z.boolean().default(false),});
const authorSchema = z.object({ name: z.string(), email: z.email().optional(), avatar: z.string().optional(), bio: z.string().optional(), social: z.object({ twitter: z.string().optional(), github: z.string().optional(), linkedin: z.string().optional(), }).optional(),});
const baseContentSchema = z.object({ title: z.string(), description: z.string().max(160), date: z.date(), draft: z.boolean().default(false), tags: z.array(z.string()).default([]), seo: seoSchema.optional(),});
// Compose base schemas into collections. Each collection declares a// Content Layer glob loader — entries are keyed by `id` (there is no// `slug` in the Content Layer API).const blogCollection = defineCollection({ loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/blog' }), schema: baseContentSchema.extend({ author: authorSchema.optional(), updated: z.date().optional(), relatedPosts: z.array(z.string()).optional(), // ids of related posts }),});
export const collections = { blog: blogCollection,};Composing collections from shared building blocks like seoSchema and authorSchema keeps field definitions consistent across content types and makes schema evolution a one-place change. See the starter’s src/content.config.ts for the full production schema set (projects, blog, navigation, bio, experience, adr).