Content collections
Production-tested setup for Content Collections - a TypeScript-first build tool that transforms local content files (Markdown/MDX) into type-safe data collections with automatic validation. Use when: building blogs, documentation sites, or content-heavy applications with Vite + React, setting up MDX content with React components, implementing type-safe content schemas with Zod, migrating from Contentlayer, or encountering TypeScript import errors with content collections. Covers: Vite plugin setup, tsconfig path aliases, collection schemas with Zod validation, MDX compilation with compileMDX, transform functions for computed properties, rehype/remark plugins, React component integration with MDXContent, Cloudflare Workers deployment, and production build optimization. Keywords: content-collections, @content-collections/core, @content-collections/vite, @content-collections/mdx, MDX, markdown, Zod schema validation, type-safe content, frontmatter, compileMDX, defineCollection, defineConfig, Vite plugin, tsconfig paths, .content-collections/generated, MDXContent component, rehype plugins, remark plugins, content schema, document transform, allPosts import, static site generation, blog setup, documentation, Cloudflare Workers static assets, content validation errors, module not found content-collections, path alias not working, MDX type errors, transform function async, collection not updatingFrom its SKILL.md
npx -y skills add jackspace/ClaudeSkillz --skill content-collectionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 16 stars16 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
16.1 KB, ~3.7k tokens by cl100k_base, as published. Nobody here has run it
Content Collections
Status: Production Ready ✅ Last Updated: 2025-11-07 Dependencies: None Latest Versions: @content-collections/[email protected], @content-collections/[email protected], [email protected]
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
content-collections.ts(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
pnpm add -D @content-collections/core @content-collections/vite zod
2. Configure TypeScript Path Alias
Add to tsconfig.json:
{
"compilerOptions": {
"paths": {
"content-collections": ["./.content-collections/generated"]
}
}
}
- Configure Vite Plugin
Add to vite.config.ts:
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
.content-collections/
5. Create Collection Config
Create content-collections.ts in project root:
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
mkdir -p content/posts
Create content/posts/first-post.md:
---
title: My First Post
date: 2025-11-07
description: Introduction to Content Collections
---
# My First Post
Content goes here...
7. Import and Use
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
contentfield 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
content-collectionsnot./content-collections - Import from wrong package -
@content-collections/corefor config,content-collectionsfor 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:
Add to tsconfig.json:
{
"compilerOptions": {
"paths": {
"content-collections": ["./.content-collections/generated"]
}
}
}
Restart TypeScript server in VS Code: Cmd+Shift+P → "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 .content-collections directory changes, which triggers regeneration.
Prevention:
- Add to
.gitignore:
.content-collections/
- Add to
vite.config.ts(if still happening):
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:
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
Issue #4: Collection Not Updating on File Change
Error: New content files not appearing in collection.
Why it happens: Glob pattern doesn't match, or dev server needs restart.
Prevention:
- Verify glob pattern matches your files:
include: "*.md" // Only root files
include: "**/*.md" // All nested files
include: "posts/*.md" // Only posts/ folder
- Restart dev server after adding new files outside watched patterns
- Check file actually saved (watch for editor issues)
Source: Common user error
Issue #5: MDX Type Errors with Shiki
Error: esbuild errors with shiki langAlias or compilation failures.
Why it happens: Version incompatibility between Shiki and Content Collections.
Prevention:
Use compatible versions:
{
"devDependencies": {
"@content-collections/mdx": "^0.2.2",
"shiki": "^1.0.0"
}
}
Check official compatibility matrix in docs before upgrading Shiki.
Source: GitHub Issue #598 (Next.js 15)
Issue #6: Custom Path Aliases in MDX Imports Fail
Error: MDX imports with @ alias don't resolve.
Why it happens: MDX compiler doesn't respect tsconfig path aliases.
Prevention:
Use relative paths in MDX imports:
<!-- ❌ Won't work -->
import Component from "@/components/Component"
<!-- ✅ Works -->
import Component from "../../components/Component"
Or configure files appender (advanced, see references/transform-cookbook.md).
Source: GitHub Issue #547
Issue #7: Unclear Validation Error Messages
Error: Cryptic Zod validation errors like "Expected string, received undefined".
Why it happens: Zod errors aren't formatted for content context.
Prevention:
Add custom error messages to schema:
schema: z.object({
title: z.string({
required_error: "Title is required in frontmatter",
invalid_type_error: "Title must be a string",
}),
date: z.string().refine(
(val) => !isNaN(Date.parse(val)),
"Date must be valid ISO date (YYYY-MM-DD)"
),
})
Source: GitHub Issue #403
Issue #8: Ctrl+C Doesn't Stop Process
Error: Dev process hangs on exit, requires kill -9.
Why it happens: File watcher not cleaning up properly.
Prevention:
This is a known issue with the watcher. Workarounds:
- Use
kill -9 <pid>when it hangs - Use
content-collections watchseparately (not plugin) for more control - Add cleanup handler in
vite.config.ts(advanced)
Source: GitHub Issue #546
Configuration Patterns
Basic Blog Collection
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(),
tags: z.array(z.string()).optional(),
content: z.string(),
}),
});
export default defineConfig({
collections: [posts],
});
Multi-Collection Setup
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(),
}),
});
const docs = defineCollection({
name: "docs",
directory: "content/docs",
include: "**/*.md", // Nested folders
schema: z.object({
title: z.string(),
category: z.string(),
order: z.number().optional(),
content: z.string(),
}),
});
export default defineConfig({
collections: [posts, docs],
});
Transform Functions (Computed Fields)
const posts = defineCollection({
name: "posts",
directory: "content/posts",
include: "*.md",
schema: z.object({
title: z.string(),
date: z.string(),
content: z.string(),
}),
transform: (post) => ({
...post,
slug: post._meta.path.replace(/\.md$/, ""),
readingTime: Math.ceil(post.content.split(/\s+/).length / 200),
year: new Date(post.date).getFullYear(),
}),
});
MDX with React Components
import { compileMDX } from "@content-collections/mdx";
const posts = defineCollection({
name: "posts",
directory: "content/posts",
include: "*.mdx",
schema: z.object({
title: z.string(),
date: z.string(),
content: z.string(),
}),
transform: async (post) => {
const mdx = await compileMDX(post.content, {
syntaxHighlighter: "shiki",
shikiOptions: {
theme: "github-dark",
},
});
return {
...post,
mdx,
slug: post._meta.path.replace(/\.mdx$/, ""),
};
},
});
React Component Integration
Using Collections in React
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
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 because:
- Build-time only (no runtime filesystem access)
- Outputs static JavaScript modules
- No Node.js dependencies in generated code
Deployment Pattern
Local Dev → content-collections build → vite build → wrangler deploy
wrangler.toml
name = "my-content-site"
compatibility_date = "2025-11-07"
[assets]
directory = "./dist"
binding = "ASSETS"
Build Script
package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"deploy": "pnpm build && wrangler deploy"
}
}
Note: Vite plugin handles content-collections build automatically!
Using Bundled Resources
Templates (templates/)
Copy-paste ready configuration files:
content-collections.ts- Basic blog setupcontent-collections-multi.ts- Multiple collectionscontent-collections-mdx.ts- MDX with syntax highlightingtsconfig.json- Complete TypeScript configvite.config.ts- Vite plugin setupblog-post.md- Example content fileBlogList.tsx- React list componentBlogPost.tsx- React MDX render componentwrangler.toml- Cloudflare Workers config
References (references/)
Deep-dive documentation for advanced topics:
schema-patterns.md- Common Zod schema patternstransform-cookbook.md- Transform function recipesmdx-components.md- MDX + React integrationdeployment-guide.md- Cloudflare Workers setup
When to load: Claude should load these when you need advanced patterns beyond basic setup.
Scripts (scripts/)
init-content-collections.sh- One-command automated setup
Dependencies
Required
{
"devDependencies": {
"@content-collections/core": "^0.12.0",
"@content-collections/vite": "^0.2.7",
"zod": "^3.23.8"
}
}
Optional (MDX)
{
"devDependencies": {
"@content-collections/markdown": "^0.1.4",
"@content-collections/mdx": "^0.2.2",
"shiki": "^1.0.0"
}
}
Official Documentation
- Official Site: https://www.content-collections.dev
- Documentation: https://www.content-collections.dev/docs
- GitHub: https://github.com/sdorra/content-collections
- Vite Plugin: https://www.content-collections.dev/docs/vite
- MDX Integration: https://www.content-collections.dev/docs/mdx
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 |
Troubleshooting
Problem: TypeScript can't find 'content-collections'
Solution: Add path alias to tsconfig.json, restart TS server.
Problem: Vite keeps restarting
Solution: Add .content-collections/ to .gitignore and Vite watch ignore.
Problem: Changes not reflecting
Solution: Restart dev server, verify glob pattern, check file saved.
Problem: MDX compilation errors
Solution: Check Shiki version compatibility, verify MDX syntax.
Problem: Validation errors unclear
Solution: Add custom error messages to Zod schema.
Complete Setup Checklist
- Installed
@content-collections/coreand@content-collections/vite - Installed
zodfor schema validation - Added path alias to
tsconfig.json - Added
contentCollections()tovite.config.ts(after react()) - Added
.content-collections/to.gitignore - Created
content-collections.tsin project root - Created content directory (e.g.,
content/posts/) - Defined collection with Zod schema
- Created first content file with frontmatter
- Imported collection in React component
- Verified types work (autocomplete)
- Tested hot reloading (change content file)
Questions? Issues?
- Check
references/directory for deep dives - Verify path alias in tsconfig.json
- Check Vite plugin order (after react())
- Review known issues above
- Check official docs: https://www.content-collections.dev/docs
What ships with it: 2 files
25.0 KB alongside SKILL.md
- README.md7.3 KB
- SKILL.json17.8 KB