Type-Safe Content Management
Managing content in modern web applications requires more than just storing markdown files. You need validation, type-safety, and developer experience. That's where Content Collections and Zod shine.
The Problem with Traditional CMS
Traditional content management has several pain points:
- ❌ Runtime errors from missing fields
- ❌ No TypeScript auto-completion
- ❌ Manual type definitions that drift from reality
- ❌ Complex validation logic scattered across codebase
Without schema validation, a simple typo in frontmatter can break your entire site at runtime!
Enter Content Collections
Content Collections provides a type-safe layer between your content files and application code:
// content-collections.ts
import { defineCollection, defineConfig } from "@content-collections/core";
import { z } from "zod";
const blog = defineCollection({
name: "blog",
directory: "content/blog",
include: "**/*.mdx",
schema: (z) => ({
title: z.string(),
date: z.coerce.date(),
excerpt: z.string(),
tags: z.array(z.string()),
featured: z.boolean().default(false),
}),
});
export default defineConfig({
collections: [blog],
});
This schema automatically generates TypeScript types that match your content structure perfectly!
Zod Schema Validation
Zod provides runtime validation with excellent TypeScript inference:
// Validation at build time
const blogSchema = z.object({
title: z.string().min(1, "Title required"),
date: z.coerce.date(),
tags: z.array(z.string()).min(1),
excerpt: z.string().max(200),
});
// TypeScript knows the exact type!
type BlogPost = z.infer<typeof blogSchema>;
Advanced Validation
Zod supports complex validation scenarios:
const projectSchema = z.object({
title: z.string(),
repoUrl: z.string().url().refine(
(url) => url.includes('github.com'),
"Must be a GitHub repository"
),
techStack: z.array(z.string()).min(1).max(10),
status: z.enum(['active', 'archived', 'planned']),
});
Type-Safe Frontend Components
Once configured, you get 100% type-safe content access:
// app/blog/page.tsx
import { allBlogs } from "content-collections";
export default function BlogIndex() {
return (
<div>
{allBlogs.map((post) => (
<article key={post.slug}>
{/* TypeScript knows all these properties exist! */}
<h2>{post.title}</h2>
<time>{post.date.toLocaleDateString()}</time>
<p>{post.excerpt}</p>
{post.featured && <span>⭐ Featured</span>}
</article>
))}
</div>
);
}
Your IDE will autocomplete all properties and catch typos before you even save the file!
Benefits
1. Catch Errors Early
Schema validation runs at build time, preventing invalid content from reaching production.
2. Perfect TypeScript Integration
No manual type definitions needed - types are inferred from your schemas.
3. Better Developer Experience
Auto-completion, inline documentation, and compile-time error checking.
4. Content Versioning
Schema changes are tracked and can be migrated systematically.
Real-World Example
Here's a complete workflow:
// 1. Define schema
const snippetSchema = z.object({
title: z.string(),
language: z.enum(["TS", "Rust", "Python", "Bash"]),
code: z.string(),
});
// 2. Create content
// content/snippets/async-patterns.json
{
"title": "Async Patterns in TypeScript",
"language": "TS",
"code": "async function fetchData() { ... }"
}
// 3. Use with full type-safety
import { allSnippets } from "content-collections";
const tsSnippets = allSnippets.filter(
s => s.language === "TS" // TypeScript knows language is the enum!
);
Conclusion
Type-safe content management isn't just a nice-to-have - it's essential for building reliable, maintainable applications. Content Collections + Zod gives you:
✅ Build-time validation
✅ Auto-generated types
✅ Excellent developer experience
✅ Runtime safety
Ready to build something? Check out our Building Modern Web Apps guide!