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.title 是 string,post.tags 是 string[],编辑器自动补全。
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 | 块级组件开始 | <Component> |
:: | 块级组件结束 | </Component> |
{prop="value"} | Props 传递 | :prop="value" |
#slotName | 具名插槽 | <template #slotName> |
:component[content] | 内联组件 | <Component>content</Component> |
3.3 自定义 Prose 组件
Content 默认使用 Prose 组件渲染 Markdown 元素(<ProseH1>、<ProseP>、<ProseCode> 等)。你可以在 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 标准化:每篇文章必须包含
title、description、category、tags - 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 组件