首页
/ Nuxt `content/` 目录实战:使用 @nuxt/content 模块构建基于文件的 CMS

Nuxt `content/` 目录实战:使用 @nuxt/content 模块构建基于文件的 CMS

2026-09-05 14:34:37作者:鲍丁臣Ursa

本文以 Nuxt 仓库中 content/ 目录文档 为核心,讲解如何在 Nuxt 项目中启用 @nuxt/content 官方模块、组织 content/ 目录下的内容文件,并通过 catch-all 路由与 <ContentRenderer> 组件完成内容页面的渲染。读完后,你可以独立完成"安装模块 → 编写 Markdown/YAML/CSV/JSON 内容 → 按 URL 查询并渲染"这一完整链路,并在 Nuxt 中搭建出无需数据库的轻量级 CMS。

content/ 目录是什么

Nuxt Content 模块会读取项目根目录下的 content/ 目录,解析其中的 .md.yml.csv.json 文件,从而为你的应用创建一个基于文件的 CMS(File-based CMS)。它提供的核心能力包括:

  • 使用内置组件渲染内容;
  • 使用类 MongoDB 的查询 API 查询内容;
  • 在 Markdown 文件中通过 MDC(Markdown Data Components)语法直接使用 Vue 组件;
  • 自动生成导航(navigation)。

content/ 是项目顶层目录,与 app/public/server/ 等目录并列。该模块属于 @nuxt/ scope 的官方模块,由 Nuxt 团队维护(可参考 生态模块说明)。模块启用后,Nuxt 会在其配置项下挂出模块配置,配置键名为 content,这一点在 Layers 文档 中给出的常见模块配置键示例(imagepiniacontent)里可以得到印证。

第一步:启用 Nuxt Content

只需一条命令即可同时完成"安装 @nuxt/content 依赖"与"把它写入 nuxt.config.tsmodules"两件事:

npx nuxt module add content

执行后,nuxt.config.ts 中会多出 modules: ['@nuxt/content'](或 content)这一配置。关于模块的加载时机,从 modules/ 目录文档 可以确认执行顺序为:

  1. 先加载 nuxt.config.ts 中声明的模块(@nuxt/content 会走这里);
  2. 再按字母序执行 modules/ 目录下的本地模块。

也就是说,content 模块在应用启动早期就已就绪,其后声明的本地模块可以在 build 阶段直接引用内容目录约定。

第二步:创建内容文件

把 Markdown 文件放入 content/ 目录即可,例如:

<!-- content/index.md -->
# Hello Content

模块会自动发现并解析这些文件,无需任何额外配置。除 .md 外,.yml(结构化数据)、.csv(表格数据)、.json(通用结构化数据)同样会被解析并纳入内容集合,供后续查询使用。

第三步:渲染内容页面(catch-all 路由)

要让每个内容文档都对应一个可访问的 URL,需要添加一个 catch-all 路由配合 <ContentRenderer> 组件。

在 Nuxt 中,catch-all 路由通过命名为 [...slug].vue 的页面文件创建,它会匹配该路径下的所有路由;从 pages 目录文档的 Catch-all Route 一节 可知,访问 /hello/world$route.params.slug 的值为 ["hello", "world"]。仓库内的测试 fixture 中也存在一个真实的多段 catch-all 用法,可参考 catch-all fixture

完整渲染示例如下(继承自原文档,可直接复制使用):

<!-- app/pages/[...slug].vue -->
<script lang="ts" setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
  return queryCollection('content').path(route.path).first()
})
</script>

<template>
  <div>
    <header><!-- ... --></header>

    <ContentRenderer
      v-if="page"
      :value="page"
    />

    <footer><!-- ... --></footer>
  </div>
</template>

这段代码里有几个值得注意的技术细节:

  • useAsyncDataroute.path 作为缓存键:切换 URL 时 key 变化,Nuxt 会重新执行查询,从而按路径拉取对应的内容文档,实现"路由驱动的内容加载";
  • queryCollection('content').path(route.path).first():这是 Nuxt Content 提供的类 MongoDB 查询 API,在 content 集合中按 path 字段匹配当前路由并取第一条记录,命中即返回该文档的完整数据;
  • <ContentRenderer v-if="page" :value="page" />:内置渲染组件负责把查询结果(含 Markdown 解析树)转换为 Vue 组件树;v-if 保证查询无结果时跳过正文渲染,页面框架(header/footer)仍然保留,可作为未命中内容时的兜底结构;
  • 页面整体保持单一根元素<div> 包裹),这是 Nuxt 页面参与路由过渡的前提,见 pages 文档的说明

查询 API 与 MDC 语法

原文档将深入内容留给了 Nuxt Content 官方文档,这里概括其两大进阶方向,供你按需深入:

  • 类 MongoDB 查询 APIqueryCollection 支持按字段条件、排序、分页等方式对内容集合进行检索,上面的 path(...).first() 只是最基础的路径匹配用法,复杂场景可组合更多查询操作符;
  • MDC(Markdown Data Components)语法:允许在 .md 文件中直接编写 Vue 组件及其属性、事件,从而把交互组件嵌入内容文档,无需为每篇文档单独建页。

这两项能力均由 @nuxt/content 模块自身实现,Nuxt 仓库提供的是 content/ 目录约定与页面侧的渲染接入点。

小结与延伸阅读

按本文链路操作后,你的项目结构大致为:

-| content/
---| index.md          # 内容文件,自动解析
---| guide/
-----| getting-started.md
-| app/
---| pages/
-----| [...slug].vue  # catch-all 路由 + <ContentRenderer>

结合仓库中的相关文档可继续深入:

登录后查看全文
热门项目推荐
相关项目推荐