**Status**: Production Ready
Manages Git-based content with type-safe queries, collections, and SQL storage for Nuxt apps.
npx claudepluginhub secondsky/claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/blog-collection.example.tsassets/content.config.example.tsassets/docs-collection.example.tsassets/nuxt.config.example.tsreferences/collection-examples.mdreferences/deployment-checklists.mdreferences/error-catalog.mdreferences/mdc-syntax-reference.mdreferences/query-operators.mdreferences/studio-setup-guide.mdscripts/deploy-cloudflare.shscripts/deploy-vercel.shscripts/setup-nuxt-content.shscripts/setup-studio.shtemplates/blog-collection-setup.tsStatus: Production Ready Last Updated: 2025-01-10 Dependencies: None Latest Versions: @nuxt/content@^3.0.0, nuxt-studio@^0.1.0-alpha, zod@^4.1.12, valibot@^0.42.0, better-sqlite3@^11.0.0
Nuxt Content v3 is a powerful Git-based CMS for Nuxt projects that manages content through Markdown, YAML, JSON, and CSV files. It transforms content files into structured data with type-safe queries, automatic validation, and SQL-based storage for optimal performance.
Major Improvements:
Use this skill when:
# Bun (recommended)
bun add @nuxt/content better-sqlite3
# npm
npm install @nuxt/content better-sqlite3
# pnpm
pnpm add @nuxt/content better-sqlite3
Why this matters:
@nuxt/content is the core CMS modulebetter-sqlite3 provides SQL storage for optimal performance// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content']
})
CRITICAL:
modules array (not buildModules)// content.config.ts
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
content: defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
tags: z.array(z.string()).optional(),
date: z.date().optional()
})
})
}
})
Create content file:
<!-- content/index.md -->
---
title: Hello World
description: My first Nuxt Content page
tags: ['nuxt', 'content']
---
# Welcome to Nuxt Content v3
This is my first content-driven site!
<!-- pages/[...slug].vue -->
<script setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () =>
queryCollection('content').path(route.path).first()
)
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>
See Full Template: templates/blog-collection-setup.ts
content.config.ts before querying2024-01-15 or 2024-01-15T10:30:00Zcontent.config.ts.only() to select specific fields (performance)components/content/ directory01-, 02-better-sqlite3 required<!--more--> in content to separate excerptscontent.config.ts.only())components/content/01- not 1-)<!--more--> dividerCollections organize related content with shared configuration:
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()).default([])
})
}),
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string()
})
})
}
})
type: 'page')Use for: Content that maps to URLs
Features:
path, title, description, body, navigationPath Mapping:
content/index.md → /
content/about.md → /about
content/blog/hello.md → /blog/hello
type: 'data')Use for: Structured data without URLs
Features:
bun add -D zod@^4.1.12
# or: npm install -D zod@^4.1.12
import { z } from 'zod'
schema: z.object({
title: z.string(),
date: z.date(),
published: z.boolean().default(false),
tags: z.array(z.string()).optional(),
category: z.enum(['news', 'tutorial', 'update'])
})
bun add -D valibot@^0.42.0
# or: npm install -D valibot@^0.42.0
import * as v from 'valibot'
schema: v.object({
title: v.string(),
date: v.date(),
published: v.boolean(false),
tags: v.optional(v.array(v.string()))
})
// Get all posts
const posts = await queryCollection('blog').all()
// Get single post by path
const post = await queryCollection('blog').path('/blog/hello').first()
// Get post by ID
const post = await queryCollection('blog').where('_id', '=', 'hello').first()
// Select specific fields (performance optimization)
const posts = await queryCollection('blog')
.only(['title', 'description', 'date', 'path'])
.all()
// Exclude fields
const posts = await queryCollection('blog')
.without(['body'])
.all()
// Single condition
const posts = await queryCollection('blog')
.where('published', '=', true)
.all()
// Multiple conditions
const posts = await queryCollection('blog')
.where('published', '=', true)
.where('category', '=', 'tutorial')
.all()
// Operators: =, !=, <, <=, >, >=, in, not-in, like
const recent = await queryCollection('blog')
.where('date', '>', new Date('2024-01-01'))
.all()
const tagged = await queryCollection('blog')
.where('tags', 'in', ['nuxt', 'vue'])
.all()
// Sort by field
const posts = await queryCollection('blog')
.sort('date', 'DESC')
.all()
// Pagination
const posts = await queryCollection('blog')
.sort('date', 'DESC')
.limit(10)
.offset(0)
.all()
// Count results
const total = await queryCollection('blog')
.where('published', '=', true)
.count()
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
const posts = await queryCollection('blog')
.where('published', '=', true)
.sort('date', 'DESC')
.all()
return { posts }
})
Auto-generate navigation tree from content structure:
const navigation = await queryCollectionNavigation('blog').all()
Returns hierarchical structure:
[
{
title: 'Getting Started',
path: '/docs/getting-started',
children: [{ title: 'Installation', path: '/docs/getting-started/installation' }]
}
]
Advanced patterns (filters, ordering, custom structures): See references/collection-examples.md
<!-- Default slot -->
::my-alert
This is an alert message
::
<!-- Named slots -->
::my-card
#title
Card Title
#default
Card content here
::
Component:
<!-- components/content/MyAlert.vue -->
<template>
<div class="alert">
<slot />
</div>
</template>
::my-button{href="/docs" type="primary"}
Click me
::
Component:
<!-- components/content/MyButton.vue -->
<script setup>
defineProps<{
href: string
type: 'primary' | 'secondary'
}>()
</script>
// Search across all content
const results = await queryCollectionSearchSections('blog', 'nuxt content')
.where('published', '=', true)
.all()
Returns:
[
{
id: 'hello',
path: '/blog/hello',
title: 'Hello World',
content: '...matching text...'
}
]
bun add -D @nuxthub/core
bunx wrangler d1 create nuxt-content
bun run build && bunx wrangler pages deploy dist
wrangler.toml:
[[d1_databases]]
binding = "DB" # Must be exactly "DB" (case-sensitive)
database_name = "nuxt-content"
database_id = "your-database-id"
CRITICAL: D1 binding MUST be named DB (case-sensitive).
See: references/deployment-checklists.md for complete Cloudflare deployment guide with troubleshooting.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
routeRules: {
'/blog/**': { prerender: true }
}
})
vercel deploy
See: references/deployment-checklists.md for complete Vercel configuration and prerender strategy.
bun add -D nuxt-studio@alpha
# or: npm install -D nuxt-studio@alpha
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content', 'nuxt-studio'],
studio: {
enabled: true,
gitInfo: {
name: 'your-name',
email: 'your-email@example.com'
}
}
})
https://yourdomain.com/api/__studio/oauth/callbackCRITICAL: Callback URL must match production domain exactly (including https://).
Error: Collection 'xyz' not found
Solution: Define collection in content.config.ts and restart dev server
rm -rf .nuxt && bun dev
Error: Validation error: Expected date, received string
Solution: Use ISO 8601 format:
---
date: 2024-01-15 # ✅ Correct
# NOT: "January 15, 2024" # ❌ Wrong
---
Error: DB is not defined
Solution: D1 binding name must be exactly DB (case-sensitive) in Cloudflare dashboard.
Error: Components show as raw text
Solution: Place components in components/content/ with exact name matching:
<!-- components/content/MyAlert.vue -->
::my-alert
Content
::
Error: New content doesn't appear in navigation
Solution: Clear .nuxt cache:
rm -rf .nuxt && bun dev
See All 18 Issues: references/error-catalog.md
Load references/error-catalog.md when:
Load references/collection-examples.md when:
Load references/query-operators.md when:
Load references/deployment-checklists.md when:
Load references/mdc-syntax-reference.md when:
Load references/studio-setup-guide.md when:
Templates (templates/):
blog-collection-setup.ts - Complete blog setup with collections, queries, navigation, search, and deployment (334 lines).only() to select specific fieldscomponents/content/<!--more--> for excerpts.nuxt after config changesThis skill composes well with:
Official Documentation:
Examples:
Production Tested: Documentation sites, blogs, content platforms Last Updated: 2025-01-27 Token Savings: ~60% (reduces content + error documentation)
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.