Перейти к содержимому

18. @nuxt/content

@nuxt/content — официальный модуль Nuxt для создания файловой CMS. Он позволяет хранить контент в виде Markdown, MDX, JSON и YAML файлов и запрашивать его через мощный API.


Окно терминала
npx nuxi module add content
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
content: {
// Подсветка синтаксиса через Shiki
highlight: {
theme: {
default: 'github-dark',
dark: 'github-dark',
light: 'github-light'
},
langs: ['js', 'ts', 'vue', 'jsx', 'tsx', 'bash', 'json', 'css', 'html', 'python']
},
// MDC (Markdown Components) синтаксис
markdown: {
anchorLinks: true,
remarkPlugins: ['remark-emoji'],
rehypePlugins: ['rehype-figure']
},
// Настройка директории
sources: {
content: {
driver: 'fs',
base: resolve(__dirname, 'content')
}
}
}
})

content/
├── index.md # /
├── about.md # /about
├── blog/
│ ├── _dir.yml # метаданные директории
│ ├── first-post.md # /blog/first-post
│ └── second-post.mdx # /blog/second-post
├── docs/
│ ├── 1.getting-started.md
│ ├── 2.configuration.md
│ └── 3.deployment.md
└── data/
├── team.json
└── config.yaml

Frontmatter — метаданные в начале файла в формате YAML:

---
title: "Моя первая статья"
description: "Описание для SEO"
date: 2024-01-15
author: "Александр"
tags: ['nuxt', 'vue', 'tutorial']
image: '/images/cover.jpg'
draft: false
featured: true
---
Текст статьи...

Доступ к frontmatter в компонентах:

<script setup lang="ts">
const { data: article } = await useAsyncData('article', () =>
queryCollection('blog').path('/blog/first-post').first()
)
// article.value.title
// article.value.description
// article.value.date
</script>

Основной способ получения контента (Nuxt Content v3):

// Получение одного документа
const article = await queryCollection('blog')
.path('/blog/my-post')
.first()
// Получение всех документов
const articles = await queryCollection('blog')
.order('date', 'DESC')
.limit(10)
.all()
// Фильтрация
const featured = await queryCollection('blog')
.where('featured', '=', true)
.where('draft', '=', false)
.order('date', 'DESC')
.all()
// Поиск
const results = await queryCollection('blog')
.where('title', 'LIKE', '%nuxt%')
.all()

<ContentDoc> автоматически находит и рендерит документ по текущему пути:

pages/blog/[slug].vue
<template>
<article>
<ContentDoc>
<template #default="{ doc }">
<h1>{{ doc.title }}</h1>
<time>{{ doc.date }}</time>
<ContentRenderer :value="doc" />
</template>
<template #not-found>
<div>Статья не найдена</div>
</template>
<template #empty>
<div>Пустой документ</div>
</template>
</ContentDoc>
</article>
</template>

<ContentRenderer> рендерит Markdown/MDX контент:

<script setup lang="ts">
const { data: doc } = await useAsyncData(
useRoute().path,
() => queryCollection('docs').path(useRoute().path).first()
)
</script>
<template>
<div>
<h1>{{ doc.title }}</h1>
<ContentRenderer :value="doc" class="prose" />
</div>
</template>

MDC позволяет использовать Vue-компоненты прямо в Markdown:

# Документация
Обычный текст **Markdown**.
::callout{type="warning"}
Это важное предупреждение!
::
::code-group
```typescript [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/content']
})
```
```bash [Terminal]
npx nuxi module add content
```
::
:badge[Новое в v3]{color="green"}
::card{title="Заголовок карточки"}
Содержимое карточки с **форматированием**.
::
components/content/Callout.vue
<script setup lang="ts">
defineProps<{
type?: 'info' | 'warning' | 'danger' | 'success'
}>()
</script>
<template>
<div :class="`callout callout-${type ?? 'info'}`">
<slot />
</div>
</template>
<style scoped>
.callout { padding: 16px; border-radius: 8px; }
.callout-warning { background: #fef3c7; border-left: 4px solid #f59e0b; }
.callout-danger { background: #fee2e2; border-left: 4px solid #ef4444; }
.callout-success { background: #d1fae5; border-left: 4px solid #10b981; }
.callout-info { background: #dbeafe; border-left: 4px solid #3b82f6; }
</style>

content/data/team.json
[
{
"name": "Александр",
"role": "Frontend Developer",
"avatar": "/images/alex.jpg"
},
{
"name": "Мария",
"role": "Backend Developer",
"avatar": "/images/maria.jpg"
}
]
// Получение JSON данных
const team = await queryCollection('data')
.path('/data/team')
.first()
// team.body — массив сотрудников

<script setup lang="ts">
// Получение предыдущего и следующего документа
const { data: surround } = await useAsyncData(
`surround-${useRoute().path}`,
() => queryCollectionItemSurroundings('docs', useRoute().path, {
fields: ['title', 'description']
})
)
const [prev, next] = surround.value ?? []
</script>
<template>
<nav>
<NuxtLink v-if="prev" :to="prev.path">
← {{ prev.title }}
</NuxtLink>
<NuxtLink v-if="next" :to="next.path">
{{ next.title }} →
</NuxtLink>
</nav>
</template>

Shiki обеспечивает красивую подсветку в code-блоках:

```typescript [filename.ts] {2,4}
const x = 1 // обычная строка
const y = 2 // подсвеченная строка
const z = 3 // обычная строка
const w = 4 // подсвеченная строка
```

Параметры code-блоков:

  • Язык: typescript, vue, bash
  • Имя файла: [filename.ts]
  • Подсветка строк: {1,3-5}

ФорматПоддержкаОсобенности
.mdСтандартный Markdown
.mdxMarkdown + JSX компоненты
.jsonДанные в JSON формате
.yamlДанные в YAML формате
.csvТабличные данные
FrontmatterYAML метаданные
MDCVue компоненты в Markdown
ShikiПодсветка 100+ языков