Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
jackspace avatar

Content Collections

  • 66 installs
  • 16 repo stars
  • Updated November 20, 2025
  • jackspace/claudeskillz

Set up Content Collections to transform local Markdown/MDX into type-safe, Zod-validated data collections for Vite + React blogs and docs sites.

About

A production-tested setup for Content Collections, a TypeScript-first tool that turns Markdown/MDX into type-safe data collections. A developer uses it when building blogs, docs sites, or content apps with Vite + React.

  • Zod schema validation and compileMDX with React components
  • Covers Vite plugin setup, tsconfig paths, and Cloudflare Workers deployment

Content Collections by the numbers

  • 66 all-time installs (skills.sh)
  • Ranked #1,168 of 2,244 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jackspace/claudeskillz --skill content-collections

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs66
repo stars16
Last updatedNovember 20, 2025
Repositoryjackspace/claudeskillz

What it does

Set up Content Collections to transform local Markdown/MDX into type-safe, Zod-validated data collections for Vite + React blogs and docs sites.

Files

SKILL.mdMarkdownGitHub ↗

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: 1. Define collections in content-collections.ts (name, directory, Zod schema) 2. CLI/plugin scans filesystem, parses frontmatter, validates against schema 3. Generates TypeScript modules in .content-collections/generated/ 4. 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"]
    }
  }
}

###

3. 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:

1. Add path alias to tsconfig.json - Required for imports to work 2. Add .content-collections to .gitignore - Generated files shouldn't be committed 3. Use Standard Schema validators - Zod, Valibot, ArkType supported 4. Include `content` field in schema - Required for frontmatter parsing 5. Await compileMDX in transforms - MDX compilation is async 6. Put contentCollections() after react() in Vite - Plugin order matters

❌ Never Do:

1. Commit .content-collections directory - Always generated, never committed 2. Use non-standard validators - Must support StandardSchema spec 3. Forget to restart dev server after config changes - Required for new collections 4. Use sync transforms with async operations - Transform must be async 5. Double-wrap path alias - Use content-collections not ./content-collections 6. Import from wrong package - @content-collections/core for config, content-collections 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:

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:

1. Add to .gitignore:

.content-collections/

2. 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:

1. Verify glob pattern matches your files:

include: "*.md"        // Only root files
include: "**/*.md"     // All nested files
include: "posts/*.md"  // Only posts/ folder

2. Restart dev server after adding new files outside watched patterns 3. 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:

1. Use kill -9 <pid> when it hangs 2. Use content-collections watch separately (not plugin) for more control 3. 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 setup
  • content-collections-multi.ts - Multiple collections
  • content-collections-mdx.ts - MDX with syntax highlighting
  • tsconfig.json - Complete TypeScript config
  • vite.config.ts - Vite plugin setup
  • blog-post.md - Example content file
  • BlogList.tsx - React list component
  • BlogPost.tsx - React MDX render component
  • wrangler.toml - Cloudflare Workers config

References (references/)

Deep-dive documentation for advanced topics:

  • schema-patterns.md - Common Zod schema patterns
  • transform-cookbook.md - Transform function recipes
  • mdx-components.md - MDX + React integration
  • deployment-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)

PackageVersionStatus
@content-collections/core0.12.0✅ Latest stable
@content-collections/vite0.2.7✅ Latest stable
@content-collections/mdx0.2.2✅ Latest stable
@content-collections/markdown0.1.4✅ Latest stable
zod3.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/core and @content-collections/vite
  • [ ] Installed zod for schema validation
  • [ ] Added path alias to tsconfig.json
  • [ ] Added contentCollections() to vite.config.ts (after react())
  • [ ] Added .content-collections/ to .gitignore
  • [ ] Created content-collections.ts in 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?

1. Check references/ directory for deep dives 2. Verify path alias in tsconfig.json 3. Check Vite plugin order (after react()) 4. Review known issues above 5. Check official docs: https://www.content-collections.dev/docs

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.