agents/openai.yaml
interface: display_name: "Nuxt Content" short_description: "Build typed, content-driven Nuxt applications with @nuxt/content." policy: allow_implicit_invocation: false
onmax/nuxt-skills · GitHub
Build typed, content-driven Nuxt applications with @nuxt/content. Use when working with content.config.ts, collections, queryCollection, Markdown or MDC rendering, content databases, hooks, custom sources, search, or Content v2 migrations.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add onmax/nuxt-skills --skill nuxt-content설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
agents/openai.yamlinterface: display_name: "Nuxt Content" short_description: "Build typed, content-driven Nuxt applications with @nuxt/content." policy: allow_implicit_invocation: false
references/advanced.md# Advanced Content APIs
## Hooks
Use hooks for project-wide build-time adjustments to existing parsers.
```ts
export default defineNuxtConfig({
hooks: {
'content:file:beforeParse'(ctx) {
if (ctx.file.id.endsWith('.md')) {
ctx.file.body = ctx.file.body.replaceAll('Old name', 'New name')
}
},
'content:file:afterParse'(ctx) {
const words = typeof ctx.file.body === 'string'
? ctx.file.body.trim().split(/\s+/).length
: 0
ctx.content.readingTime = Math.ceil(words / 180)
},
},
})
```
Fields added after parsing must exist in the target collection schema or typed queries will discard their contract.
## Transformers
Use a transformer when parsing or enrichment is a reusable content-processing unit, especially for custom file extensions.
```ts
// transformers/title-suffix.ts
import { defineTransformer } from '@nuxt/content'
export default defineTransformer({
name: 'title-suffix',
extensions: ['.md'],
transform(file) {
return { ...file, title: `${file.title} · Docs` }
},
})
```
```ts
export default defineNuxtConfig({
content: {
build: {
transformers: ['~~/transformers/title-suffix'],
},
},
})
```
## Custom sources
Use `defineCollectionSource` for APIs or stores that can expose a deterministic key inventory and fetch each item by key.
```ts
import { defineCollection, defineCollectionSource, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
const releases = defineCollectionSource({
async getKeys() {
return await $fetch<string[]>('https://example.com/releases/keys')
},
async getItem(key) {
return await $fetch(`https://example.com/releases/${key}`)
},
})
export default defineContentConfig({
collections: {
releases: defineCollection({
type: 'data',
source: releases,
schema: z.object({ title: z.string(), publishedAt: z.string() }),
}),
},
})
```
Stable keys are the incremental-build identity. Authentication stays in the build environment because source collection happens during content generation.
## Raw content and search
Add `rawbody: z.string()` to a collection schema when production code genuinely needs the source body. Nuxt Content fills that field automatically; setting `rawbody: ''` in a document suppresses it for that file. Shipping raw content increases payload and database size, so select it only in queries that consume it.
Use `queryCollectionSearchSections` to produce heading-level search records. Pair the result with Nuxt UI Content Search, MiniSearch, Fuse, or another search engine; keep presentation and ranking behavior in that consumer.
## Debugging and migration
For local SQLite, inspect `.data/content/contents.sqlite` after starting Nuxt. If generated content is stale, run `npx nuxi cleanup`, restart, and recheck the collection source before resetting the database.
When migrating from Content v2:
- Replace `queryContent()` with collection-scoped `queryCollection()`.
- Replace navigation, surroundings, and search helpers with their `queryCollection*` equivalents.
- Create an explicit catch-all Nuxt page for document-driven routing.
- Use `.navigation.yml`, `path`, and the v3 built-in fields.
- Render through `ContentRenderer`; use native slots with `mdc-unwrap` in content components.
## Checks
- Hooks and transformers produce fields allowed by the collection schema.
- Custom source keys remain stable across identical upstream data.
- Raw content and search data are selected only where consumed.
- A migrated route resolves the same URL and 404 behavior under SSR and client navigation.
Official references: [hooks](https://content.nuxt.com/docs/advanced/hooks), [transformers](https://content.nuxt.com/docs/advanced/transformers), [custom sources](https://content.nuxt.com/docs/advanced/custom-source), [raw content](https://content.nuxt.com/docs/advanced/raw-content), [migration](https://content.nuxt.com/docs/getting-started/migration).
references/collections.md# Collections
Use collections as the schema and routing boundary for content. Once `content.config.ts` exists, Nuxt Content imports only files matched by a collection source.
## Choose the collection type
- `page` adds `path`, `title`, `description`, `seo`, `body`, and `navigation`; use it when one content file maps to one route.
- `data` adds the common file fields but leaves the domain schema to you; use it for authors, products, configuration, and other structured records.
- Every collection includes `id`, `stem`, `extension`, and `meta`.
Keep each source file in one collection. Overlapping collection globs can break live reload, so use `exclude` to make ownership explicit.
## Validators and editor metadata
Import validators from their packages. The `z` re-export from `@nuxt/content` is deprecated.
```ts
import { defineCollection, defineContentConfig, property } from '@nuxt/content'
import { z } from 'zod/v4'
export default defineContentConfig({
collections: {
products: defineCollection({
type: 'data',
source: 'products/*.json',
schema: z.object({
sku: z.string(),
category: z.string(),
price: z.number(),
image: property(z.string()).editor({ input: 'media' }),
}),
indexes: [
{ columns: ['category', 'price'] },
{ columns: ['sku'], unique: true },
],
}),
},
})
```
Nuxt Content supports Zod 3, Zod 4, and Valibot through JSON Schema conversion. Use `property(schema).editor(...)` for Studio input metadata and `property(schema).inherit(...)` when a schema should follow a Vue component's props. Keep indexes beside the collection they optimize; `columns`, `unique`, and `name` are collection index options.
## Source shapes
```ts
// Local content directory
source: 'docs/**'
// Explicit include, exclusions, and route prefix
source: {
include: 'fr/**',
exclude: ['fr/drafts/**'],
prefix: '/fr',
}
// Files outside content/
source: {
cwd: resolve('packages/handbook'),
include: '**/*.md',
}
// Remote Git repository
source: {
repository: {
url: 'https://github.com/org/docs',
branch: 'main',
auth: { token: process.env.DOCS_TOKEN },
},
include: 'content/**',
}
```
Remote repositories require `include`. Keep credentials in environment variables and use the narrowest repository token that can read the source.
For API-backed content, use `defineCollectionSource` with stable `getKeys()` results and a `getItem(key)` implementation. The returned items still pass through the collection schema, so keys and field types must be deterministic.
## Navigation and locale sources
Navigation metadata lives in `.navigation.yml`. A source such as `**/*.md` excludes that file; use `**` or an include pattern that covers Markdown and YAML when navigation is required.
```ts
const common = z.object({ locale: z.enum(['en', 'fr']) })
export default defineContentConfig({
collections: {
docs_en: defineCollection({
type: 'page',
source: { include: 'en/**', prefix: '/' },
schema: common,
}),
docs_fr: defineCollection({
type: 'page',
source: { include: 'fr/**', prefix: '/fr' },
schema: common,
}),
},
})
```
Pick the collection from the active locale, then query its generated path. For `@nuxtjs/i18n` route behavior, apply that module's guidance after the collection boundary is settled.
## Checks
- `npx nuxi typecheck` resolves every collection and custom field.
- Each source glob includes the intended files and excludes competing collections.
- Fields added by hooks or transformers exist in the collection schema.
- Query indexes cover actual filter/sort patterns rather than every field.
Official references: [define collections](https://content.nuxt.com/docs/collections/define), [sources](https://content.nuxt.com/docs/collections/sources), [validators](https://content.nuxt.com/docs/collections/validators).
references/config.md# Configuration and deployment
## Database adapters
Nuxt Content builds a database dump and restores it into the configured adapter at runtime. SQLite is the default.
```ts
export default defineNuxtConfig({
content: {
database: {
type: 'sqlite',
filename: '.data/content/contents.sqlite',
},
},
})
```
Supported adapters include:
- `sqlite` with a file path or `:memory:`; select `experimental.sqliteConnector` when the runtime needs `better-sqlite3`, `sqlite3`, Bun SQLite, or Node's native SQLite.
- `d1` with a Cloudflare binding name.
- `postgresql` with `pg` installed and a connection URL.
- `libsql` with `@libsql/client`, URL, and optional auth token.
- `pglite` with `@electric-sql/pglite`; keep it to development-oriented deployments unless the chosen host provides durable storage.
```ts
export default defineNuxtConfig({
content: {
database: {
type: 'd1',
bindingName: 'CONTENT_DB',
},
experimental: {
sqliteConnector: 'native',
},
},
})
```
Database indexes belong in `defineCollection({ indexes: [...] })`, because each collection owns its table and query patterns.
## Build and renderer options
```ts
export default defineNuxtConfig({
content: {
build: {
markdown: {
toc: { depth: 3, searchDepth: 2 },
remarkPlugins: {
'remark-gfm': {},
},
rehypePlugins: {
'rehype-external-links': {
target: '_blank',
rel: ['noopener', 'noreferrer'],
},
},
highlight: {
themes: { default: 'github-light', dark: 'github-dark' },
langs: ['ts', 'vue', 'bash'],
},
},
csv: { json: true, delimiter: ',' },
},
renderer: {
alias: { a: 'DocsLink' },
anchorLinks: { h2: true, h3: true },
},
watch: { enabled: true },
},
})
```
The watcher is a development feature. Transformers are registered under `content.build.transformers`; load [Advanced](advanced.md) when processing behavior extends beyond remark/rehype plugins.
## Deployment boundary
- Static generation packages queried content into the generated application.
- Server and serverless targets restore the generated dump on first query; an in-memory SQLite database is recreated on cold start.
- Cloudflare Workers use D1 when content must share a durable runtime database.
- NuxtHub can supply the database configuration through `@nuxthub/core`; apply the `nuxthub` skill for provider and binding details.
- Vercel, Netlify, AWS, Docker, and other targets have provider-specific presets; verify the selected adapter against the deployment filesystem and runtime.
Keep database URLs and tokens in private runtime configuration or build environment variables. Remote collection credentials are build-time inputs; database credentials are runtime inputs when the adapter connects after deployment.
## Checks
- The deployment runtime supports the chosen driver and its peer dependency.
- Production can restore or reach the database after a cold start.
- Shiki languages and Markdown plugins are limited to the actual content set.
- A clean build and a production-mode query both succeed.
Official references: [configuration](https://content.nuxt.com/docs/getting-started/configuration), [deployment](https://content.nuxt.com/docs/deploy/server), [NuxtHub](https://content.nuxt.com/docs/deploy/nuxthub).
references/querying.md# Querying
## Page and list queries
Wrap initial render queries in `useAsyncData` or `useFetch` so Nuxt can transfer the result in its payload.
```ts
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
return queryCollection('docs').path(route.path).first()
})
const { data: posts } = await useAsyncData('published-posts', () => {
return queryCollection('blog')
.where('draft', '=', false)
.order('date', 'DESC')
.select('path', 'title', 'description', 'date')
.limit(12)
.all()
})
```
The builder supports `where`, `andWhere`, `orWhere`, `order`, `select`, `skip`, `limit`, `all`, `first`, and `count`. Use grouped conditions when SQL precedence matters.
```ts
const posts = await queryCollection('blog')
.where('published', '=', true)
.andWhere(group => group
.where('date', '>', '2026-01-01')
.where('category', '=', 'news'))
.all()
```
## Nitro queries
Pass the event as the first argument on the server so the query uses the request's Content context.
```ts
export default defineEventHandler(async (event) => {
const { slug } = getRouterParams(event)
return queryCollection(event, 'docs').path(`/${slug}`).first()
})
```
Extend `../.nuxt/tsconfig.server.json` from `server/tsconfig.json` when server-side collection types are missing.
## Navigation and surroundings
```ts
const navigation = await queryCollectionNavigation('docs', [
'title',
'description',
'icon',
])
const [previous, next] = await queryCollectionItemSurroundings(
'docs',
route.path,
{ fields: ['title', 'description'] },
)
```
Navigation reads page paths plus `.navigation.yml` metadata. Surroundings returns a two-item tuple, with `null` at either boundary.
## Search
`queryCollectionSearchSections` turns headings and their text into a search index suitable for Nuxt UI, MiniSearch, Fuse, or another client-side search engine.
```ts
const { data: sections } = await useAsyncData('docs-search', () => {
return queryCollectionSearchSections('docs', {
ignoredTags: ['style', 'script'],
extraFields: ['description'],
})
})
```
Use `useSearchCollection` when the app needs reactive, built-in search over a collection rather than managing an external index. Load the search data once per stable collection and avoid rebuilding an index on every keystroke.
## Checks
- A stable `useAsyncData` key identifies each payload-backed query.
- Client and server calls use their correct signatures.
- `select()` retains every field the renderer consumes.
- Pagination applies a deterministic `order()` before `skip()` and `limit()`.
- Search and navigation source globs include the files that generate their metadata.
Official references: [queryCollection](https://content.nuxt.com/docs/utils/query-collection), [navigation](https://content.nuxt.com/docs/utils/query-collection-navigation), [surroundings](https://content.nuxt.com/docs/utils/query-collection-item-surroundings), [search sections](https://content.nuxt.com/docs/utils/query-collection-search-sections).
references/rendering.md# Rendering
## ContentRenderer
Query a page document, handle the missing state at the route boundary, then pass the document to `ContentRenderer`.
```vue
<script setup lang="ts">
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
return queryCollection('docs').path(route.path).first()
})
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found' })
}
</script>
<template>
<ContentRenderer :value="page" />
</template>
```
Nuxt Content v3 renders documents through `ContentRenderer`; document-driven routing still belongs in an explicit Nuxt page such as `app/pages/[...slug].vue`.
## MDC components
Components under `components/content/` are available in Markdown and MDC.
```md
:icon{name="lucide:star"}
::callout{type="warning"}
Review this before publishing.
::
::card
#title
Typed collections
#default
The body is rendered through the default slot.
::
```
Use native slots in content components. The `mdc-unwrap` attribute removes Markdown wrapper elements when a component needs the raw slot shape.
```vue
<template>
<aside class="callout">
<slot mdc-unwrap="p" />
</aside>
</template>
```
Content components are registered for MDC rendering. Register them separately when they also need dynamic use outside Markdown.
## Prose components
Override semantic Markdown output with `ProseP`, `ProseA`, `ProseH1`–`ProseH6`, `ProseCode`, `ProsePre`, `ProseImg`, `ProseTable`, and the matching list/blockquote components. Inline code maps to `ProseCode`; fenced code maps to `ProsePre`.
Keep accessible HTML semantics and forward the attributes supplied by MDC. Let a design-system skill own styling decisions when Nuxt UI or another component library is installed.
## Markdown processing and highlighting
Configure Markdown under `content.build.markdown` and renderer aliases under `content.renderer`.
```ts
export default defineNuxtConfig({
content: {
build: {
markdown: {
toc: { depth: 3, searchDepth: 2 },
highlight: {
themes: {
default: 'github-light',
dark: 'github-dark',
},
langs: ['ts', 'vue', 'bash'],
},
},
},
renderer: {
alias: { a: 'DocsLink' },
anchorLinks: { h2: true, h3: true },
},
},
})
```
Load only the Shiki languages used by the content set. When custom remark or rehype plugins are involved, verify the rendered AST and the final SSR HTML.
Official references: [Markdown and MDC](https://content.nuxt.com/docs/files/markdown), [ContentRenderer](https://content.nuxt.com/docs/components/content-renderer), [prose components](https://content.nuxt.com/docs/components/prose).
SKILL.md---
name: nuxt-content
description: Build typed, content-driven Nuxt applications with @nuxt/content. Use when working with content.config.ts, collections, queryCollection, Markdown or MDC rendering, content databases, hooks, custom sources, search, or Content v2 migrations.
license: MIT
---
# Nuxt Content
## Workflow
1. Inspect the installed `@nuxt/content` version, `content.config.ts`, matching files under `content/`, and the Nuxt surface that consumes them. The branch is understood when the collection, source glob, schema, and consumer agree.
2. Open the smallest matching guide below. Apply Nuxt Content guidance only to content-owned APIs; use Nuxt, Nuxt Studio, Nuxt UI, or Vue guidance when those packages own the remaining surface.
3. Implement with typed collections and payload-backed Nuxt data loading. The change is complete when collection types resolve, the intended query returns the expected document shape, and the rendered route works in the target rendering mode.
## Routing
| Task | Open |
| ------------------------------------------------------------------------------------ | ---------------------------------------- |
| Collection types, schemas, indexes, local/remote sources, or locale prefixes | [Collections](references/collections.md) |
| Filtering, sorting, pagination, navigation, surroundings, or search | [Querying](references/querying.md) |
| Markdown, MDC, `ContentRenderer`, prose components, or code highlighting | [Rendering](references/rendering.md) |
| Database adapters, markdown processing, renderer aliases, or deployment storage | [Configuration](references/config.md) |
| Hooks, transformers, custom sources, raw content, debugging, or Content v2 migration | [Advanced](references/advanced.md) |
| Visual editing, authentication, media, drafts, or Git publishing | `nuxt-studio` skill |
| Writing or restructuring documentation prose | `document-writer` skill |
## Baseline
```ts
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
docs: defineCollection({
type: 'page',
source: 'docs/**',
schema: z.object({
updatedAt: z.date().optional(),
}),
}),
},
})
```
```vue
<script setup lang="ts">
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
return queryCollection('docs').path(route.path).first()
})
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>
```
Use `npx nuxi typecheck` to verify generated collection types. When a query is empty, confirm that its collection source includes the file—especially `.navigation.yml`—before changing query logic.