Nuxt Content 内容管理实战

@nuxt/content 把文件系统变成了一个内容数据库——Markdown 文件放进 content/ 目录,就能通过类 ORM 的 API 查询、渲染、搜索。它是构建博客、文档站、帮助中心的利器,也是 Nuxt 生态中最成熟的内容管理方案。本章从 Content 的架构设计讲起,深入查询 API、MDC 语法、搜索和导航树的工程实践。

1. 架构设计:文件即数据库

1.1 核心理念

传统 CMS(WordPress、Strapi)将内容存储在数据库中,通过管理后台编辑。@nuxt/content 走了一条不同的路:文件即内容

content/
├── blog/
│   ├── 2025-01-getting-started.md
│   └── 2025-02-advanced-tips.md
├── docs/
│   ├── 1.introduction.md
│   └── 2.configuration.md
└── help/
    └── faq.yml

每个文件会被解析为一个"文档"对象,包含:

  • Front Matter(YAML 头)→ 文档元数据(title、description、date、tags)
  • Body(正文)→ 解析为 AST,可渲染为 Vue 组件树
  • 路径信息 → 自动映射为 URL(content/blog/hello.md/blog/hello

1.2 为什么不用数据库

方面文件系统数据库
版本控制Git 天然支持,diff / 回滚 / PR 审查需要额外实现版本历史
编辑体验VS Code / 任何编辑器,Markdown 生态需要管理后台
部署与代码一起部署,CDN 友好需要独立数据库服务
协作Git 分支 + PR,开发者友好多人编辑冲突处理
实时编辑需要重新部署(或用 Studio)原生支持

适用场景判断:如果内容由开发者或技术写作者维护(文档、博客、帮助中心),文件系统方案更优。如果内容由非技术人员频繁编辑(商品描述、营销页),传统 CMS 更合适。

1.3 Content v3 的新特性

Nuxt Content v3(适配 Nuxt 4)引入了重大架构变化:

  • SQL 存储层:内容不再直接从文件读取,而是构建时写入 SQLite(通过 D1 或本地文件),查询基于 SQL 引擎,性能大幅提升
  • Collections API:用 TypeScript 定义内容集合的 schema,获得完整类型推断
  • Markdown 解析优化:基于 mdast 的新解析管线,支持 GFM、数学公式、代码高亮

2. queryContent:内容查询 API

2.1 基础查询

queryContent() 返回一个链式查询构建器,语法类似 Drizzle 或 Mongoose:

// 获取所有博客文章,按日期倒序
const { data: posts } = await useAsyncData('blog', () =>
  queryContent('blog')
    .sort({ date: -1 })
    .find()
)
 
// 获取单篇文章
const { data: post } = await useAsyncData('post', () =>
  queryContent('blog')
    .where({ _path: '/blog/getting-started' })
    .findOne()
)

2.2 查询操作符

方法作用示例
.where()条件过滤.where({ tags: { $contains: 'nuxt' } })
.sort()排序.sort({ date: -1, title: 1 })
.limit()限制数量.limit(10)
.skip()跳过条目.skip(10)(分页)
.only()选择字段.only(['title', 'date', '_path'])
.without()排除字段.without(['body'])(列表页不需要正文)
.find()返回数组列表查询
.findOne()返回单条详情查询
.count()返回数量统计

2.3 Content v3 的 Collections 查询

Content v3 引入了类型安全的 Collections API:

// content.config.ts
import { defineCollection, z } from '@nuxt/content'
 
export const collections = {
  blog: defineCollection({
    source: 'blog/**',
    type: 'page',
    schema: z.object({
      title: z.string(),
      date: z.date(),
      tags: z.array(z.string()),
      cover: z.string().optional(),
      draft: z.boolean().default(false),
    }),
  }),
}

定义 schema 后,查询结果自动获得 TypeScript 类型推断——post.titlestringpost.tagsstring[],编辑器自动补全。

2.4 性能优化

  • 列表页用 .only():文章列表只需要 title、date、description,不需要 body。.only() 避免传输大量正文数据。
  • 缓存查询结果useAsyncData 的 key 参数确保相同查询不会重复执行。在 nuxt.config 中配置 content.experimental.cacheContents: true 开启内容缓存。
  • 分页:大量内容时用 .skip() + .limit() 分页加载,而不是一次性获取所有文档。

3. MDC 语法:Markdown 中的 Vue 组件

3.1 什么是 MDC

MDC(Markdown Components)是 Nuxt Content 独创的语法,让你在 Markdown 中使用 Vue 组件:

<!-- 块级组件 -->
::callout{type="warning"}
这是一个警告提示框。
::
 
<!-- 内联组件 -->
这段文字包含 :badge[新功能]{color="green"} 标记。
 
<!-- 带插槽的组件 -->
::card
#title
视频教程
 
#description
学习如何使用 AI 生成视频。
::

3.2 语法规则

语法含义Vue 等价
::component块级组件开始&lt;Component&gt;
::块级组件结束&lt;/Component&gt;
&#123;prop="value"&#125;Props 传递:prop="value"
#slotName具名插槽&lt;template #slotName&gt;
:component[content]内联组件&lt;Component&gt;content&lt;/Component&gt;

3.3 自定义 Prose 组件

Content 默认使用 Prose 组件渲染 Markdown 元素(&lt;ProseH1&gt;&lt;ProseP&gt;&lt;ProseCode&gt; 等)。你可以在 components/content/ 目录覆盖它们:

<!-- components/content/ProseCode.vue -->
<template>
  <div class="code-block">
    <div class="code-header">
      <span>{{ language }}</span>
      <button @click="copy">复制</button>
    </div>
    <pre><code><slot /></code></pre>
  </div>
</template>

这让你在不修改 Markdown 内容的情况下,完全控制渲染样式和交互。

3.4 MDC 的适用边界

MDC 很强大,但不要过度使用:

  • 适合:提示框、标签、卡片、代码演示等通用展示组件
  • 不适合:复杂的交互组件(表单、图表)——这些应该放在 Vue 页面中,而非 Markdown

如果你发现 Markdown 文件中 MDC 组件比普通文字还多,说明这部分内容更适合用 Vue 页面实现。

4. 全文搜索

4.1 内置搜索引擎

Content v3 内置了基于 MiniSearch 的全文搜索引擎,零配置即可使用:

// nuxt.config.ts
export default defineNuxtConfig({
  content: {
    search: {
      enabled: true,
    },
  },
})

客户端使用 searchContent() composable:

const { data: results } = await searchContent('AI 视频生成')

4.2 搜索的工作原理

构建时,Content 模块将所有文档的文本内容提取并建立倒排索引(inverted index)。这个索引文件作为静态资源部署,客户端搜索时加载索引文件在本地匹配——不需要服务端 API

优势:速度极快(纯客户端),不依赖外部搜索服务。 局限:索引文件随内容量增长,超过 1000 篇文章时索引可能 > 1MB,影响加载。

4.3 大规模内容的搜索方案

当内容量超过搜索引擎的舒适区时,考虑外部方案:

方案特点适用规模
MiniSearch(内置)零配置,纯客户端< 500 篇
Algolia DocSearch免费(开源项目)、毫秒级任意规模
Meilisearch开源自托管、类 Algolia 体验任意规模
Typesense开源、typo-tolerant任意规模

5. 导航树

5.1 自动生成导航

Content 根据文件系统结构自动生成导航树。文件名的数字前缀决定排序:

content/docs/
├── 1.getting-started/
│   ├── 1.installation.md
│   └── 2.configuration.md
├── 2.features/
│   ├── 1.data-fetching.md
│   └── 2.state-management.md
└── 3.deployment.md

使用 fetchContentNavigation() 获取导航树:

const { data: navigation } = await useAsyncData('navigation', () =>
  fetchContentNavigation()
)

返回的是嵌套树结构,可以直接渲染为侧边栏导航。

5.2 导航元数据

在文档的 Front Matter 中可以控制导航行为:

---
title: "快速开始"
navigation:
  title: "快速开始"  # 导航中显示的标题(可以比 title 短)
  icon: "rocket"      # 导航图标
navigation: false      # 从导航中隐藏此文档
---

5.3 面包屑和上下篇

Content 提供 useContentHead() 和周边工具来实现常见的导航模式:

  • 面包屑:从当前文档路径解析出层级(docs > features > data-fetching
  • 上下篇queryContent().findSurround(path) 返回当前文档的前一篇和后一篇
  • TOC(目录):文档的 body.toc 属性自动包含标题树,可渲染为页内目录

6. 帮助中心实战

6.1 目录结构

content/help/
├── getting-started/
│   ├── 1.create-account.md
│   ├── 2.first-video.md
│   └── 3.subscription.md
├── features/
│   ├── 1.ai-generation.md
│   ├── 2.video-editing.md
│   └── 3.sharing.md
└── troubleshooting/
    ├── 1.upload-failed.md
    └── 2.playback-issues.md

6.2 关键设计决策

  • URL 结构/help/getting-started/create-account——层级清晰,SEO 友好
  • 搜索优先:帮助中心的首要导航方式是搜索,侧边栏是辅助
  • Front Matter 标准化:每篇文章必须包含 titledescriptioncategorytags
  • Catch-all 路由:用 pages/help/[...slug].vue 动态渲染所有帮助文档
  • MDC 组件:封装 ::steps(步骤引导)、::screenshot(截图展示)等帮助中心专用组件

6.3 内容与代码分离的优势

帮助中心的内容更新频率远高于代码。使用 Content 后:

  • 运营人员提交 Markdown PR,开发者 review 后合并——流程清晰
  • 内容变更不触发应用重新构建(如果使用 Nuxt Content Studio 或增量构建)
  • 内容可以单独翻译(配合 i18n,不同语言放在不同目录)

本章小结

  • 文件即数据库:Git 版本控制 + Markdown 编辑器生态,适合开发者和技术写作者维护的内容
  • queryContent:类 ORM 链式查询,支持 where / sort / limit / only,Content v3 的 Collections 提供类型安全
  • MDC 语法:在 Markdown 中嵌入 Vue 组件,适合通用展示组件,不适合复杂交互
  • 全文搜索:内置 MiniSearch 零配置可用,大规模内容用 Algolia / Meilisearch
  • 导航树:文件名数字前缀控制排序,fetchContentNavigation() 自动生成嵌套树
  • 实战建议:帮助中心用 catch-all 路由 + 搜索优先 + 标准化 Front Matter + 自定义 MDC 组件