
Nuxt Content
- 2.1k installs
- 696 repo stars
- Updated July 27, 2026
- onmax/nuxt-skills
nuxt-content is an agent skill that Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/AP.
About
Progressive guidance for content driven Nuxt apps with typed collections and SQL backed queries Content collections content config ts defineCollection Remote sources GitHub repos external APIs via defineCollectionSource Content queries queryCollection navigation search MDC rendering ContentRenderer prose components Database configuration SQLite PostgreSQL D1 LibSQL Content hooks content file beforeParse content file afterParse i18n multi language content NuxtStudio or preview mode LLMs integration nuxt llms For writing documentation use document writer skill For Nuxt basics use nuxt skill For NuxtHub deployment use nuxthub skill NuxtHub v1 compatible Read specific files based on current work references collections md references collections md defineCollection schemas sources content config ts references querying md references querying md queryCollection navigation search surroundings references rendering md references rendering md ContentRenderer MDC syntax prose components Shiki references config md references config md Database setup markdown plugins renderer options references studio md references studio md NuxtStudio integration preview mode live editing
- description: Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (lo
- Progressive guidance for content-driven Nuxt apps with typed collections and SQL-backed queries.
- - Content collections (`content.config.ts`, `defineCollection`)
- Follow nuxt-content SKILL.md steps and documented constraints.
- Follow nuxt-content SKILL.md steps and documented constraints.
Nuxt Content by the numbers
- 2,119 all-time installs (skills.sh)
- +41 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #558 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
nuxt-content capabilities & compatibility
- Capabilities
- description: use when working with nuxt content · progressive guidance for content driven nuxt app · content collections (`content.config.ts`, `def · follow nuxt content skill.md steps and documente
- Use cases
- orchestration
What nuxt-content says it does
description: Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/API sources), queryCollection API, MDC rendering, database configurat
Progressive guidance for content-driven Nuxt apps with typed collections and SQL-backed queries.
- Content collections (`content.config.ts`, `defineCollection`)
npx skills add https://github.com/onmax/nuxt-skills --skill nuxt-contentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 696 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | onmax/nuxt-skills ↗ |
When should an agent use nuxt-content and what problem does it solve?
Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/API sources), queryCollection API, MDC rendering, database configuration, NuxtStud
Who is it for?
Developers invoking nuxt-content as documented in the skill source.
Skip if: Skip when requirements fall outside nuxt-content documented scope.
When should I use this skill?
Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/API sources), queryCollection API, MDC rendering, database configuration, NuxtStud
What you get
Outputs aligned with the nuxt-content SKILL.md workflow and stated deliverables.
- content.config.ts
- queryCollection queries
- MDC-rendered pages
Files
Nuxt Content v3
Progressive guidance for content-driven Nuxt apps with typed collections and SQL-backed queries.
When to Use
Working with:
- Content collections (
content.config.ts,defineCollection) - Remote sources (GitHub repos, external APIs via
defineCollectionSource) - Content queries (
queryCollection, navigation, search) - MDC rendering (
<ContentRenderer>, prose components) - Database configuration (SQLite, PostgreSQL, D1, LibSQL)
- Content hooks (
content:file:beforeParse,content:file:afterParse) - i18n multi-language content
- NuxtStudio or preview mode
- LLMs integration (
nuxt-llms)
For writing documentation: use document-writer skill For Nuxt basics: use nuxt skill For NuxtHub deployment: use nuxthub skill (NuxtHub v1 compatible)
Available Guidance
Read specific files based on current work:
- [references/collections.md](references/collections.md) - defineCollection, schemas, sources, content.config.ts
- [references/querying.md](references/querying.md) - queryCollection, navigation, search, surroundings
- [references/rendering.md](references/rendering.md) - ContentRenderer, MDC syntax, prose components, Shiki
- [references/config.md](references/config.md) - Database setup, markdown plugins, renderer options
- [references/studio.md](references/studio.md) - NuxtStudio integration, preview mode, live editing
Loading Files
Consider loading these reference files based on your task:
- [ ] references/collections.md - if setting up collections, schemas, or content.config.ts
- [ ] references/querying.md - if using queryCollection, navigation, or search
- [ ] references/rendering.md - if rendering markdown/MDC or working with ContentRenderer
- [ ] references/config.md - if configuring database, markdown plugins, or renderer options
- [ ] references/studio.md - if integrating NuxtStudio or preview mode
DO NOT load all files at once. Load only what's relevant to your current task.
Key Concepts
| Concept | Purpose |
|---|---|
| Collections | Typed content groups with schemas |
| Page vs Data | page = routes + body, data = structured data only |
| Remote sources | source.repository for GitHub, defineCollectionSource for APIs |
| queryCollection | SQL-like fluent API for content |
| MDC | Vue components inside markdown |
| ContentRenderer | Renders parsed markdown body |
Quick Start
// content.config.ts
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**',
schema: z.object({
title: z.string(),
date: z.date(),
}),
}),
},
})<!-- pages/blog/[...slug].vue -->
<script setup lang="ts">
const { data: page } = await useAsyncData(
() => queryCollection('blog').path(useRoute().path).first()
)
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>Verify setup: Run npx nuxi typecheck to confirm collection types resolve. If queryCollection returns empty, check that content files exist in the path matching your source glob.
Directory Structure
project/
├── content/ # Content files
│ ├── blog/ # Maps to 'blog' collection
│ └── .navigation.yml # Navigation metadata
├── components/content/ # MDC components
└── content.config.ts # Collection definitionsOfficial Documentation
- Nuxt Content: https://content.nuxt.com
- MDC syntax: https://content.nuxt.com/docs/files/markdown#mdc-syntax
- Collections: https://content.nuxt.com/docs/collections/collections
Token Efficiency
Main skill: ~300 tokens. Each sub-file: ~800-1200 tokens. Only load files relevant to current task.
Collections
When to Use
Setting up content.config.ts, defining collection schemas, or configuring content sources.
Defining Collections
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod' // Import z from 'zod' directly (not from @nuxt/content)
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
date: z.date(),
tags: z.array(z.string()).optional(),
image: z.string().optional(),
}),
}),
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
avatar: z.string(),
twitter: z.string().optional(),
}),
}),
},
})Note: In v3.7.0+, the z re-export from @nuxt/content was deprecated. Always import from zod directly.
Collection Types
| Type | Use Case | Includes |
|---|---|---|
page | Content with routes | path, title, description, seo, body, navigation |
data | Structured data only | id, stem, extension, meta |
Page collections auto-generate: path from file location, title from first H1, description from first paragraph.
Data collections for non-routable content like authors, settings, translations.
Schema Definition
Use Zod (or other validators like Valibot since v3.7+) for type-safe schemas:
// Using Zod
import { z } from 'zod'
schema: z.object({
// Required fields
title: z.string(),
// Optional with defaults
draft: z.boolean().default(false),
// Arrays
tags: z.array(z.string()).optional(),
// Dates (parsed from frontmatter)
publishedAt: z.date(),
// Enums
status: z.enum(['draft', 'published', 'archived']),
// Nested objects
author: z.object({
name: z.string(),
email: z.string().email(),
}).optional(),
})Multi-validator support (v3.7+): Nuxt Content supports multiple schema validators including Zod v4 and Valibot via the standard schema spec. Import your preferred validator directly.
Source Patterns
// Single directory
source: 'blog/**/*.md'
// Multiple patterns
source: ['posts/**/*.md', 'articles/**/*.md']
// Exclude patterns
source: {
include: 'docs/**/*.md',
exclude: ['docs/internal/**', 'docs/**/_*.md'],
}
// Single CSV file (v3.10+)
source: 'data/products.csv'Remote Sources (GitHub)
Pull content from external repositories:
export default defineContentConfig({
collections: {
nuxtDocs: defineCollection({
type: 'page',
source: {
repository: 'https://github.com/nuxt/content',
include: 'docs/content/**',
prefix: '/docs',
// Optional: shallow clone for faster fetching (v3.10+)
shallow: true,
},
}),
},
})Private repositories:
source: {
repository: 'https://github.com/org/private-repo',
include: 'docs/**/*.md',
authToken: process.env.GITHUB_TOKEN, // GitHub PAT
}Bitbucket with basic auth:
source: {
repository: 'https://bitbucket.org/org/repo',
include: '**/*.md',
authBasic: { username: 'user', password: process.env.BITBUCKET_PASSWORD },
}Custom API Sources
Fetch content from any API using defineCollectionSource:
import { defineCollection, defineCollectionSource, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
const apiSource = defineCollectionSource({
getKeys: async () => {
const items = await fetch('https://api.example.com/posts').then(r => r.json())
return items.map((item: { id: string }) => `${item.id}.json`)
},
getItem: async (key: string) => {
const id = key.replace('.json', '')
return fetch(`https://api.example.com/posts/${id}`).then(r => r.json())
},
})
export default defineContentConfig({
collections: {
posts: defineCollection({
type: 'data',
source: apiSource,
schema: z.object({
title: z.string(),
content: z.string(),
}),
}),
},
})Path Extraction
File paths become content properties:
content/blog/2024/my-post.md
└─────┬────┘
stem: "blog/2024/my-post"
path: "/blog/2024/my-post"Override path in frontmatter:
---
path: /custom-url
---Navigation Metadata
Control navigation behavior per-file:
---
navigation:
title: Short Nav Title
icon: heroicons:home
---Or per-directory with .navigation.yml:
# content/blog/.navigation.yml
title: Blog Posts
icon: heroicons:newspaperBest Practices
| Do | Don't |
|---|---|
Use page for routable content | Use page for config/data files |
| Define explicit schemas | Rely on implicit types |
| Use Zod defaults for optional fields | Leave required fields without validation |
| Colocate related content | Scatter files across unrelated directories |
Common Patterns
Blog with categories:
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
category: z.enum(['tech', 'life', 'news']),
date: z.date(),
featured: z.boolean().default(false),
}),
})Documentation with ordering:
docs: defineCollection({
type: 'page',
source: 'docs/**/*.md',
schema: z.object({
order: z.number().default(999),
section: z.string().optional(),
}),
})Schema extension and inheritance (v3.8+):
// Base schema with common fields
const baseSchema = z.object({
title: z.string(),
description: z.string().optional(),
})
// Extended schema with additional properties
const blogSchema = baseSchema.extend({
author: z.string(),
date: z.date(), // Auto-cast to date string in v3.11+
tags: z.array(z.string()).optional(),
})
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: blogSchema,
})Hidden properties in editor (v3.11+):
// Define properties that exist in content but shouldn't be shown in the editor UI
import { property } from '@nuxt/content'
schema: z.object({
title: z.string(),
// Hidden from editor without redefining validation
internalId: property(z.string()).hidden(),
})Raw content access:
// Magic field - include rawbody to access original content
docs: defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
rawbody: z.string(), // Auto-filled with raw markdown
}),
})
// Exclude per-file: add `rawbody: ''` in frontmatteri18n with per-locale collections:
// content.config.ts - separate collection per language
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
const commonSchema = z.object({ title: z.string() })
export default defineContentConfig({
collections: {
content_en: defineCollection({ type: 'page', source: { include: 'en/**', prefix: '' }, schema: commonSchema }),
content_fr: defineCollection({ type: 'page', source: { include: 'fr/**', prefix: '' }, schema: commonSchema }),
},
})
// pages/[...slug].vue
const collection = (`content_${locale.value}`) as keyof Collections
const page = await queryCollection(collection).path(slug).first()Inherit component prop types (v3.7+):
import { defineCollection, defineContentConfig, property } from '@nuxt/content'
import { z } from 'zod'
defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
// Use property().inherit() to inherit Vue component props
hero: property(z.object({})).inherit('app/components/HeroComponent.vue'),
title: z.string(),
}),
})This allows schema fields to automatically match Vue component prop types.
Resources
- Collections: https://content.nuxt.com/docs/collections/collections
- Schema: https://content.nuxt.com/docs/collections/schema
- Sources: https://content.nuxt.com/docs/collections/sources
Configuration
When to Use
Setting up database backend, configuring markdown processing, or customizing renderer behavior.
Database Configuration
Content uses SQL for queries. Configure in nuxt.config.ts:
SQLite (Default)
export default defineNuxtConfig({
content: {
database: {
type: 'sqlite',
filename: '.data/content.db',
// Optional: add database indexes for better query performance (v3.10+)
// Recommended for large sites or frequently queried fields
indexes: [
{ fields: ['path'] },
{ fields: ['date', 'draft'] },
{ fields: ['category'] },
],
},
},
})PostgreSQL
export default defineNuxtConfig({
content: {
database: {
type: 'postgresql',
url: process.env.DATABASE_URL,
},
},
})Cloudflare D1
export default defineNuxtConfig({
content: {
database: {
type: 'd1',
bindingName: 'DB', // Matches wrangler.toml binding
},
},
})LibSQL / Turso
export default defineNuxtConfig({
content: {
database: {
type: 'libsql',
url: process.env.TURSO_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
},
},
})PGlite (In-memory)
export default defineNuxtConfig({
content: {
database: {
type: 'pglite',
},
},
})Local Development Database
Use different database for dev vs production:
export default defineNuxtConfig({
content: {
database: {
type: 'd1',
bindingName: 'DB',
},
// Dev-only SQLite
_localDatabase: {
type: 'sqlite',
filename: '.data/content-dev.db',
},
},
})Markdown Configuration
export default defineNuxtConfig({
content: {
build: {
markdown: {
// Table of contents
toc: {
depth: 3, // Max heading depth
searchDepth: 2, // Search depth in tree
},
// Extract title from first H1
contentHeading: true,
// Remark plugins (markdown → mdast)
remarkPlugins: {
'remark-emoji': {},
'remark-gfm': { singleTilde: false },
},
// Rehype plugins (mdast → hast)
rehypePlugins: {
'rehype-external-links': {
target: '_blank',
rel: ['noopener', 'noreferrer'],
},
},
// Code highlighting
highlight: {
theme: 'github-dark',
langs: ['js', 'ts', 'vue', 'css', 'html', 'bash', 'yaml', 'json'],
},
},
},
},
})Highlight Themes
Single theme:
highlight: {
theme: 'github-dark',
}Multi-theme (light/dark):
highlight: {
themes: {
default: 'github-light',
dark: 'github-dark',
},
}Available themes: github-dark, github-light, dracula, nord, one-dark-pro, etc. See Shiki themes.
Renderer Configuration
export default defineNuxtConfig({
content: {
renderer: {
// Component aliases for prose
alias: {
p: 'MyParagraph',
h2: 'MyHeading2',
code: 'MyCodeBlock',
},
// Anchor links per heading level
anchorLinks: {
h1: false,
h2: true,
h3: true,
h4: false,
h5: false,
h6: false,
},
},
},
})File Type Configuration
YAML
export default defineNuxtConfig({
content: {
build: {
yaml: {
// YAML parser options
},
// Or disable YAML parsing
yaml: false,
},
},
})CSV
export default defineNuxtConfig({
content: {
build: {
csv: {
json: true, // Parse as JSON objects
delimiter: ',', // Column delimiter
},
},
},
})Experimental Options
export default defineNuxtConfig({
content: {
experimental: {
// Use Node.js native SQLite (Node.js v22.5.0+, v3.4+)
nativeSqlite: true,
// Specify SQLite connector (v3.6+)
// 'better-sqlite3' moved to peer dependency in v3.6.0
sqliteConnector: 'better-sqlite3', // or 'native' (Node 22+), 'sqlite3'
},
},
})Note: Starting v3.6.0, better-sqlite3 is a peer dependency. The module will prompt you to install your preferred SQLite connector on first run.
Full Configuration Example
export default defineNuxtConfig({
content: {
database: {
type: 'sqlite',
filename: '.data/content.db',
},
build: {
markdown: {
toc: { depth: 3, searchDepth: 2 },
remarkPlugins: {
'remark-gfm': {},
},
highlight: {
themes: { default: 'github-light', dark: 'github-dark' },
langs: ['vue', 'ts', 'bash', 'yaml', 'json'],
},
},
},
renderer: {
anchorLinks: { h2: true, h3: true },
},
},
})Environment Variables
Common env vars for database:
# PostgreSQL
DATABASE_URL=postgresql://user:pass@host:5432/db
# Turso/LibSQL
TURSO_URL=libsql://your-db.turso.io
TURSO_AUTH_TOKEN=your-token
# GitHub (for remote sources)
GITHUB_TOKEN=ghp_xxxxContent Hooks
Modify content during build:
// nuxt.config.ts
export default defineNuxtConfig({
hooks: {
'content:file:beforeParse': function (ctx) {
// Modify raw content before parsing
if (ctx.file.id.endsWith('.md')) {
ctx.file.body = ctx.file.body.replace(/oldTerm/gi, 'newTerm')
}
},
'content:file:afterParse': function (ctx) {
// Add computed fields after parsing
const wordCount = ctx.file.body?.split(/\s+/).length || 0
ctx.content.readingTime = Math.ceil(wordCount / 180)
},
'content:document:generated': function (ctx) {
// Hook fires when auto-generated markdown versions are created (v3.11+)
// Use to modify or process generated markdown
},
},
})Note: Fields added in afterParse must be defined in your collection schema:
schema: z.object({ readingTime: z.number().optional() })Date field casting (v3.11+): Date fields are automatically cast to date strings in proper format.
LLMs Integration
Generate AI-ready content with nuxt-llms:
export default defineNuxtConfig({
modules: ['@nuxt/content', 'nuxt-llms'],
llms: {
domain: 'https://your-site.com',
title: 'Your Site',
sections: [
{
title: 'Docs',
contentCollection: 'docs',
contentFilters: [{ field: 'draft', operator: '<>', value: true }],
},
],
},
})Auto-generates /llms.txt for LLM consumption.
Performance
Database indexes (v3.10+): For large sites or collections with frequent queries on specific fields, add indexes to improve query performance:
database: {
type: 'sqlite',
indexes: [
{ fields: ['path'] }, // Single field
{ fields: ['date', 'draft'] }, // Composite index
],
}SQL transactions (v3.11+): Queries are automatically wrapped in transactions for better performance and consistency.
Best Practices
| Do | Don't |
|---|---|
Use _localDatabase for dev/prod split | Use production DB in development |
| Specify only needed langs | Load all Shiki languages |
| Use multi-theme for dark mode support | Hardcode single theme |
| Configure TOC depth for your content | Use defaults without checking |
| Add indexes for frequently queried fields | Index every field unnecessarily |
Resources
- Configuration: https://content.nuxt.com/docs/getting-started/configuration
- Database: https://content.nuxt.com/docs/getting-started/configuration#database
- Markdown: https://content.nuxt.com/docs/getting-started/configuration#markdown
Querying Content
When to Use
Using queryCollection(), building navigation, implementing search, or getting prev/next items.
Query Builder
const posts = await queryCollection('blog')
.where('draft', '=', false)
.order('date', 'DESC')
.limit(10)
.all()
// Single item
const post = await queryCollection('blog')
.where('path', '=', '/blog/my-post')
.first()
// Count
const total = await queryCollection('blog')
.where('category', '=', 'tech')
.count()Operators
| Operator | Example | Description |
|---|---|---|
= | where('status', '=', 'published') | Exact match |
<> | where('status', '<>', 'draft') | Not equal |
>, <, >=, <= | where('order', '>', 5) | Comparison |
IN | where('tag', 'IN', ['vue', 'nuxt']) | Match any in array |
BETWEEN | where('date', 'BETWEEN', [start, end]) | Range inclusive |
LIKE | where('title', 'LIKE', '%vue%') | Pattern match |
IS NULL | where('image', 'IS NULL', true) | Null check |
IS NOT NULL | where('image', 'IS NOT NULL', true) | Not null |
Complex Queries
// AND conditions
const posts = await queryCollection('blog')
.where('draft', '=', false)
.andWhere(group => group
.where('category', '=', 'tech')
.orWhere('featured', '=', true)
)
.all()
// OR conditions
const posts = await queryCollection('blog')
.where('author', '=', 'john')
.orWhere('author', '=', 'jane')
.all()Select Fields
// Select specific fields (reduces payload)
const titles = await queryCollection('blog')
.select('title', 'path', 'date')
.all()Navigation
Generate hierarchical navigation trees:
// In pages/[...slug].vue or composables
const navigation = await queryCollectionNavigation('docs')
// With custom fields
const navigation = await queryCollectionNavigation('docs', ['title', 'icon', 'description'])Returns nested structure:
[
{
title: 'Getting Started',
path: '/docs/getting-started',
children: [
{ title: 'Installation', path: '/docs/getting-started/installation' },
{ title: 'Configuration', path: '/docs/getting-started/configuration' },
]
}
]Navigation control via frontmatter:
---
navigation: false # Exclude from nav
---Or with custom title:
---
navigation:
title: Short Title
icon: heroicons:home
---Surroundings (Prev/Next)
const { prev, next } = await queryCollectionItemSurroundings(
'docs',
'/docs/current-page',
{ before: 1, after: 1 }
)
// With specific fields
const { prev, next } = await queryCollectionItemSurroundings(
'docs',
currentPath,
{ before: 1, after: 1, fields: ['title', 'path', 'description'] }
)Search Sections
Split pages into searchable sections:
const sections = await queryCollectionSearchSections('docs', {
minHeading: 2, // Minimum heading level to index (v3.10+)
maxHeading: 4, // Maximum heading level to index (v3.10+)
})
// Returns
[
{
id: 'docs:getting-started#installation',
title: 'Installation',
titles: ['Getting Started', 'Installation'],
content: 'Section text content...',
path: '/docs/getting-started',
}
]
// Include extra fields (v3.4+)
const sections = await queryCollectionSearchSections('docs', {
minHeading: 2,
maxHeading: 3,
fields: ['description', 'category'],
})Server-Side Queries
In server routes, pass the event:
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
return await queryCollection(event, 'blog')
.where('draft', '=', false)
.all()
})Common Patterns
Latest posts:
const latest = await queryCollection('blog')
.where('draft', '=', false)
.order('date', 'DESC')
.limit(5)
.all()Posts by tag:
const tagged = await queryCollection('blog')
.where('tags', 'LIKE', `%${tag}%`)
.all()Paginated list:
const page = 1
const perPage = 10
const posts = await queryCollection('blog')
.order('date', 'DESC')
.skip((page - 1) * perPage)
.limit(perPage)
.all()Featured + recent:
const [featured, recent] = await Promise.all([
queryCollection('blog').where('featured', '=', true).first(),
queryCollection('blog').order('date', 'DESC').limit(5).all(),
])Best Practices
| Do | Don't |
|---|---|
Use .select() to reduce payload | Fetch all fields when only need few |
| Cache navigation queries | Rebuild navigation on every page |
Use .first() for single items | Use .all()[0] |
| Pass event in server routes | Omit event on server side |
Utility Functions (v3.6+)
Helper functions for common navigation patterns:
// Find page headline (first H1)
const headline = findPageHeadline(page)
// Get breadcrumb trail
const breadcrumb = findPageBreadcrumb(navigation, '/docs/collections/schema')
// Returns: [{ title: 'Docs', path: '/docs' }, { title: 'Collections', path: '/docs/collections' }, ...]
// Get immediate children of a page
const children = findPageChildren(navigation, '/docs/collections')
// Get siblings (prev/next at same level)
const siblings = findPageSiblings(navigation, '/docs/collections/schema')Resources
- Query API: https://content.nuxt.com/docs/querying/query-collection
- Navigation: https://content.nuxt.com/docs/querying/query-collection-navigation
- Search: https://content.nuxt.com/docs/querying/query-collection-search-sections
Rendering Content
For writing style/structure: see document-writer skillWhen to Use
Working with <ContentRenderer>, MDC syntax, custom prose components, or code highlighting.
ContentRenderer
Render parsed markdown body:
<script setup lang="ts">
const post = await queryCollection('blog')
.where('path', '=', '/blog/my-post')
.first()
</script>
<template>
<ContentRenderer v-if="post" :value="post" />
</template>With custom wrapper:
<ContentRenderer :value="post">
<template #default="{ body }">
<article class="prose">
<component :is="body" />
</article>
</template>
</ContentRenderer>MDC Syntax
Use Vue components inside markdown:
<!-- Inline component -->
:icon{name="heroicons:star"}
<!-- Block component -->
::callout{type="warning"}
This is a warning message.
::
<!-- With slots -->
::card
#title
Card Title
#default
Card content goes here.
::
<!-- Nested components -->
::grid{cols="2"}
::card
First card
::
::card
Second card
::
::Component Props
<!-- String props -->
:badge{label="New"}
<!-- Boolean props -->
::collapse{open}
Content
::
<!-- Object/array props (YAML) -->
## ::chart
data:
- value: 10
- value: 20
---
::MDC Component Location
Components in components/content/ are auto-registered for MDC:
components/
└── content/
├── Callout.vue → ::callout
├── ProseCode.vue → Code blocks
└── ProseH2.vue → ## headingsProse Components
Override default HTML elements with custom components:
| Element | Component | Markdown |
|---|---|---|
<p> | ProseP | Paragraphs |
<h1>-<h6> | ProseH1-ProseH6 | # headings |
<a> | ProseA | [link](url) |
<code> | ProseCode | ` code ` |
<pre> | ProsePre | Code blocks |
<ul>, <ol> | ProseUl, ProseOl | Lists |
<img> | ProseImg |  |
<table> | ProseTable | Tables |
<blockquote> | ProseBlockquote | > quotes |
Custom prose component:
<!-- components/content/ProseH2.vue -->
<template>
<h2 :id="id" class="group">
<a :href="`#${id}`" class="anchor">
<slot />
</a>
</h2>
</template>
<script setup lang="ts">
defineProps<{ id?: string }>()
</script>Code Highlighting
Shiki provides syntax highlighting. Configure in nuxt.config.ts:
export default defineNuxtConfig({
content: {
build: {
markdown: {
highlight: {
theme: 'github-dark',
// Or multi-theme
themes: {
default: 'github-light',
dark: 'github-dark',
},
// Additional languages
langs: ['vue', 'typescript', 'bash', 'yaml'],
},
},
},
},
})In markdown:
````md
const foo = 'bar'<template>
<div>Hello</div>
</template>````
Line highlighting:
```md ``ts {2,4-6} const a = 1 const b = 2 // highlighted const c = 3 const d = 4 // highlighted const e = 5 // highlighted const f = 6 // highlighted
Filename display:
```md ``ts [nuxt.config.ts] export default defineNuxtConfig({})
Custom Components Example
Alert component:
<!-- components/content/Alert.vue -->
<template>
<div :class="['alert', `alert-${type}`]">
<slot />
</div>
</template>
<script setup lang="ts">
withDefaults(defineProps<{ type?: 'info' | 'warning' | 'error' }>(), {
type: 'info',
})
</script>Usage in markdown:
::alert{type="warning"}
Be careful with this operation.
::Table of Contents
Access TOC from parsed content:
<script setup lang="ts">
const post = await queryCollection('blog').where('path', '=', route.path).first()
const toc = post?.body?.toc?.links || []
</script>
<template>
<nav>
<ul>
<li v-for="link in toc" :key="link.id">
<a :href="`#${link.id}`">{{ link.text }}</a>
</li>
</ul>
</nav>
</template>Best Practices
| Do | Don't |
|---|---|
| Use MDC for reusable content patterns | Embed raw HTML in markdown |
| Create semantic prose components | Override prose without purpose |
| Use Shiki themes matching your design | Mix multiple highlight libraries |
| Leverage slots for flexible components | Hardcode all component content |
Resources
- MDC Syntax: https://content.nuxt.com/docs/files/markdown#mdc-syntax
- Prose Components: https://content.nuxt.com/docs/components/prose
- ContentRenderer: https://content.nuxt.com/docs/components/content-renderer
NuxtStudio & Preview Mode
When to Use
Setting up NuxtStudio integration, enabling preview mode, or configuring live content editing.
NuxtStudio Overview
NuxtStudio is a visual editor for Nuxt Content sites. It provides:
- Visual WYSIWYG editing
- Real-time preview
- Git-based workflow
- Component editing
Enable Studio
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
content: {
studio: {
enabled: true,
},
},
})Preview Mode
Preview mode allows editing content before publishing.
Development Preview
In development, content updates via WebSocket HMR automatically.
Production Preview
Enable production preview API:
// nuxt.config.ts
export default defineNuxtConfig({
content: {
preview: {
enabled: true,
// Optional: custom API route
api: '/__preview',
},
},
})Preview Token
Secure preview mode with a token:
// nuxt.config.ts
export default defineNuxtConfig({
content: {
preview: {
enabled: true,
token: process.env.PREVIEW_TOKEN,
},
},
})Access preview: https://your-site.com?preview=your-token
Using Preview in Components
<script setup lang="ts">
const { enabled: previewEnabled } = useContentPreview()
</script>
<template>
<div v-if="previewEnabled" class="preview-banner">
Preview Mode Active
</div>
</template>Preview API Routes
Content exposes preview endpoints:
POST /__preview/start - Start preview session
POST /__preview/stop - End preview session
GET /__preview/status - Check preview statusGit Integration
Studio uses Git for version control:
// nuxt.config.ts
export default defineNuxtConfig({
content: {
studio: {
enabled: true,
git: {
// Branch for preview changes
branch: 'content-preview',
},
},
},
})Schema for Studio Editor
Add editor hints to your schema:
// 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().describe('The post title'),
description: z.string().describe('SEO description'),
image: z.string().describe('Cover image URL'),
date: z.date().describe('Publication date'),
tags: z.array(z.string()).describe('Post tags'),
}),
}),
},
})The .describe() method adds labels in Studio's editor UI.
Studio Configuration
// nuxt.config.ts
export default defineNuxtConfig({
content: {
studio: {
enabled: true,
// Custom Studio URL (for self-hosted)
url: 'https://studio.nuxt.com',
},
},
})Live Editing Components
Mark components as editable in Studio:
<!-- components/content/Hero.vue -->
<template>
<div data-content-id="hero" class="hero">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</template>
<script setup lang="ts">
defineProps<{
title: string
description: string
}>()
</script>Environment Setup
# .env
NUXT_CONTENT_STUDIO_ENABLED=true
NUXT_CONTENT_PREVIEW_ENABLED=true
NUXT_CONTENT_PREVIEW_TOKEN=your-secret-tokenWebSocket HMR (Development)
Content automatically syncs changes in development:
// nuxt.config.ts
export default defineNuxtConfig({
content: {
watch: {
// Watch for file changes
enabled: true,
// Debounce updates
debounce: 500,
},
},
})Deployment Considerations
Vercel
// nuxt.config.ts
export default defineNuxtConfig({
content: {
studio: {
enabled: process.env.VERCEL_ENV === 'preview',
},
},
})Cloudflare Pages
// nuxt.config.ts
export default defineNuxtConfig({
content: {
studio: {
enabled: process.env.CF_PAGES_BRANCH !== 'main',
},
},
})Common Patterns
Preview banner component:
<!-- components/PreviewBanner.vue -->
<script setup lang="ts">
const { enabled } = useContentPreview()
</script>
<template>
<div v-if="enabled" class="fixed top-0 left-0 right-0 bg-yellow-500 text-center py-1 z-50">
Preview Mode - <button @click="navigateTo(useRoute().fullPath.replace('?preview', ''))">Exit</button>
</div>
</template>Conditional preview logic:
const { enabled } = useContentPreview()
const posts = await queryCollection('blog')
.where('draft', '=', enabled ? undefined : false) // Show drafts in preview
.all()Best Practices
| Do | Don't |
|---|---|
| Use preview token in production | Expose preview without auth |
| Enable studio only in preview envs | Enable studio in production |
Use .describe() for schema fields | Leave schema undocumented |
| Test preview mode before deploy | Assume preview works |
Resources
- NuxtStudio: https://nuxt.studio
- Preview Mode: https://content.nuxt.com/docs/studio/preview
- Studio Setup: https://content.nuxt.com/docs/studio/setup
Related skills
FAQ
What is nuxt-content?
Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/API sources), queryCollection API, MDC rendering, database con
When should I use nuxt-content?
Use when working with Nuxt Content v3, markdown content, or CMS features in Nuxt - provides collections (local/remote/API sources), queryCollection API, MDC rendering, database con
Is nuxt-content safe to install?
Review the Security Audits panel on this page before production use.