Content Collections
Status: Production Ready ✅
Last Updated: 2025-11-07
Dependencies: None
Latest Versions: @content-collections/core@0.12.0, @content-collections/vite@0.2.7, zod@3.23.8
What is Content Collections?
Content Collections transforms local content files (Markdown/MDX) into type-safe TypeScript data with automatic validation at build time.
Problem it solves: Manual content parsing, lack of type safety, runtime errors from invalid frontmatter.
How it works:
- Define collections in (name, directory, Zod schema)
- CLI/plugin scans filesystem, parses frontmatter, validates against schema
- Generates TypeScript modules in
.content-collections/generated/
- Import collections:
import { allPosts } from "content-collections"
Perfect for: Blogs, documentation sites, content-heavy apps with Cloudflare Workers, Vite, Next.js.
Quick Start (5 Minutes)
1. Install Dependencies
bash
# Bun (recommended)
bun add -d @content-collections/core @content-collections/vite zod
# npm
npm install -D @content-collections/core @content-collections/vite zod
# pnpm
pnpm add -D @content-collections/core @content-collections/vite zod
2. Configure TypeScript Path Alias
json
{
"compilerOptions": {
"paths": {
"content-collections": ["./.content-collections/generated"]
}
}
}
- Configure Vite Plugin
typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import contentCollections from "@content-collections/vite";
export default defineConfig({
plugins: [
react(),
contentCollections(), // MUST come after react()
],
});
4. Update .gitignore
5. Create Collection Config
typescript
import { defineCollection, defineConfig } from "@content-collections/core";
import { z } from "zod";
const posts = defineCollection({
name: "posts",
directory: "content/posts",
include: "*.md",
schema: z.object({
title: z.string(),
date: z.string(),
description: z.string(),
content: z.string(),
}),
});
export default defineConfig({
collections: [posts],
});
6. Create Content Directory
Create
content/posts/first-post.md
:
markdown
---
title: My First Post
date: 2025-11-07
description: Introduction to Content Collections
---
# My First Post
Content goes here...
7. Import and Use
typescript
import { allPosts } from "content-collections";
console.log(allPosts); // Fully typed!
Result: Type-safe content with autocomplete, validation, and HMR.
Critical Rules
✅ Always Do:
- Add path alias to tsconfig.json - Required for imports to work
- Add .content-collections to .gitignore - Generated files shouldn't be committed
- Use Standard Schema validators - Zod, Valibot, ArkType supported
- Include field in schema - Required for frontmatter parsing
- Await compileMDX in transforms - MDX compilation is async
- Put contentCollections() after react() in Vite - Plugin order matters
❌ Never Do:
- Commit .content-collections directory - Always generated, never committed
- Use non-standard validators - Must support StandardSchema spec
- Forget to restart dev server after config changes - Required for new collections
- Use sync transforms with async operations - Transform must be async
- Double-wrap path alias - Use not
- Import from wrong package -
@content-collections/core
for config, for data
Known Issues Prevention
Issue #1: Module not found: 'content-collections'
Error:
Cannot find module 'content-collections' or its corresponding type declarations
Why it happens: Missing TypeScript path alias configuration.
Prevention:
json
{
"compilerOptions": {
"paths": {
"content-collections": ["./.content-collections/generated"]
}
}
}
Restart TypeScript server in VS Code:
→ "TypeScript: Restart TS Server"
Source: Common user error
Issue #2: Vite Constant Restart Loop
Error: Dev server continuously restarts, infinite loop.
Why it happens: Vite watching
directory changes, which triggers regeneration.
Prevention:
- Add to :
- Add to (if still happening):
typescript
export default defineConfig({
server: {
watch: {
ignored: ["**/.content-collections/**"],
},
},
});
Source: GitHub Issue #591 (TanStack Start)
Issue #3: Transform Types Not Reflected
Error: TypeScript types don't match transformed documents.
Why it happens: TypeScript doesn't automatically infer transform function return type.
Prevention:
Explicitly type your transform return:
typescript
const posts = defineCollection({
name: "posts",
// ... schema
transform: (post): PostWithSlug => ({ // Type the return!
...post,
slug: post._meta.path.replace(/\.md$/, ""),
}),
});
type PostWithSlug = {
// ... schema fields
slug: string;
};
Source: GitHub Issue #396
Issues #4-8: Advanced Troubleshooting
Additional issues covered in
references/advanced-troubleshooting.md
:
| Issue | Error | Quick Fix |
|---|
| #4 | Collection not updating | Verify glob pattern, restart dev server |
| #5 | MDX/Shiki errors | Use compatible versions (shiki ^1.0.0) |
| #6 | MDX path aliases fail | Use relative paths in MDX imports |
| #7 | Unclear validation errors | Add custom Zod error messages |
| #8 | Ctrl+C doesn't stop | Use or separate watch command |
Configuration Patterns
| Pattern | Use Case | Template |
|---|
| Basic Blog | Single collection, Markdown only | templates/content-collections.ts
|
| Multi-Collection | Posts + Docs, nested folders | templates/content-collections-multi.ts
|
| Transform Functions | Computed fields (slug, readingTime) | See references/transform-cookbook.md
|
| MDX + React | Syntax highlighting, React components | templates/content-collections-mdx.ts
|
For detailed schema patterns (dates, tags, validation), load
references/schema-patterns.md
.
React Component Integration
Using Collections in React
tsx
import { allPosts } from "content-collections";
export function BlogList() {
return (
<ul>
{allPosts.map((post) => (
<li key={post._meta.path}>
<h2>{post.title}</h2>
<p>{post.description}</p>
<time>{post.date}</time>
</li>
))}
</ul>
);
}
Rendering MDX Content
tsx
import { MDXContent } from "@content-collections/mdx/react";
export function BlogPost({ post }: { post: { mdx: string } }) {
return (
<article>
<MDXContent code={post.mdx} />
</article>
);
}
Cloudflare Workers Deployment
Content Collections is
perfect for Cloudflare Workers (build-time only, no runtime filesystem). Use template
for config.
Pattern:
→
(Vite plugin handles content-collections automatically)
For detailed deployment guide, load
references/deployment-guide.md
.
Bundled Resources
Templates (9 copy-paste files)
,
content-collections-multi.ts
,
content-collections-mdx.ts
,
,
,
,
,
,
Scripts
init-content-collections.sh
- One-command automated setup
Dependencies
Required
json
{
"devDependencies": {
"@content-collections/core": "^0.12.0",
"@content-collections/vite": "^0.2.7",
"zod": "^3.23.8"
}
}
Optional (MDX)
json
{
"devDependencies": {
"@content-collections/markdown": "^0.1.4",
"@content-collections/mdx": "^0.2.2",
"shiki": "^1.0.0"
}
}
Official Documentation
Package Versions (Verified 2025-11-07)
| Package | Version | Status |
|---|
| @content-collections/core | 0.12.0 | ✅ Latest stable |
| @content-collections/vite | 0.2.7 | ✅ Latest stable |
| @content-collections/mdx | 0.2.2 | ✅ Latest stable |
| @content-collections/markdown | 0.1.4 | ✅ Latest stable |
| zod | 3.23.8 | ✅ Latest stable |
Quick Troubleshooting
| Problem | Solution |
|---|
| TypeScript can't find module | Add path alias to , restart TS server |
| Vite keeps restarting | Add to |
| Changes not reflecting | Restart dev server, verify glob pattern |
| MDX compilation errors | Check Shiki version compatibility |
| Validation errors unclear | Add custom Zod error messages |
When to Load References
| Reference | Load When... |
|---|
| Setting up complex schemas, date validation, optional fields |
| Adding computed fields, async transforms, slugs |
| Integrating React components in MDX, syntax highlighting |
| Deploying to Cloudflare Workers or other platforms |
advanced-troubleshooting.md
| Issues #4-8 (file watching, path aliases, process hanging) |
Complete Setup Checklist