惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Threatpost
WordPress大学
WordPress大学
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
PCI Perspectives
PCI Perspectives
T
The Exploit Database - CXSecurity.com
Y
Y Combinator Blog
雷峰网
雷峰网
爱范儿
爱范儿
The Hacker News
The Hacker News
Last Week in AI
Last Week in AI
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
S
Securelist
宝玉的分享
宝玉的分享
L
LangChain Blog
O
OpenAI News
AI
AI
P
Privacy International News Feed
L
LINUX DO - 最新话题
D
DataBreaches.Net
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
M
MIT News - Artificial intelligence
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
博客园 - 【当耐特】
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
K
Kaspersky official blog
Webroot Blog
Webroot Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Blog — PlanetScale
Blog — PlanetScale
MyScale Blog
MyScale Blog
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog

Ray's Blog

Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog Ray's Blog
Ray's Blog
i@mk1.io (Ra · 2025-11-12 · via Ray's Blog

把博客用 Nuxt Content 重写及踩坑记录

这次把博客用 Nuxt Content 重写了一遍,记录一下整个重写过程。当然,过程中也踩了不少坑,一并记录一下以帮助大家少走弯路。

这次重写有个有趣的地方:因为我想从零开始制作一个样式独特的简约风博客,但是奈何没有什么设计天赋,于是选择了 Vibe Coding。用的是 v0.dev,虽然生成的产物是 React 代码,但照猫画虎比起从头学设计、想布局、调样式简单多了,而且 v0 使用 shadcn 作为组件库,在 Vue 生态中有 shadcn-vue 可以无缝切换,如果获得了满意的设计那把 React 转成 Vue 的过程也不过是一些机械性劳动,并没有太大的困难。经过 50 多轮迭代后得到了一个大体还算满意的设计稿,然后就开始手动把 React 组件转换成 Vue 组件了(其实依旧是使唤 Copilot Agent 来改)。

首页
首页
文章列表
文章列表
分类页面
分类页面

在审查了一下设计后,发现分类页面的设计是所有页面里最糟糕的, 巨大的卡片的信息密度很低,也不美观。可是正如前文所言,我没有设计天赋,实在想不出更好的设计方案了。在网络上寻找灵感时,偶然看到了 Noah 的分类页面设计,真是简洁大方。在征询了 Noah 的同意后,我使用了他的设计,在此非常感谢 Noah 的慷慨许可!

Noah 的邮件
Noah 的邮件

至此,整体设计已大致完成,后面便是实现数据获取与踩坑了。

图片存储方面,我选择了 Cloudflare R2 + PicGo Old School 的组合。Cloudflare R2 的免费额度非常慷慨:

项目免费额度(每月)
存储10 GB
A 类操作1,000,000 次请求
B 类操作10,000,000 次请求
出网流量免费

什么叫互联网大善人,这就是互联网大善人(大赞

而 PicGo 是一个非常好用的开源图床客户端,记得在几年前我还在玩 hexo 博客时就用过它。配置好 Cloudflare R2 存储桶和 tinypng 图片压缩,图床就搞定了。

正片开始!

我想给 Shiki 加入一个渲染空格的 Transformer(没有实装)。但是,在查找了 Nuxt Content 和 MDC 的文档后我并没能找到添加自定义 Shiki Transformer 的地方。Nuxt Content 的文档里只提到可以通过 content.build.markdown.highlight 来配置部分选项,并没有提到如何添加 transformer。而 MDC 的文档 这玩意儿真能叫文档吗 也是只字未提,只能找到和 Nuxt Content 类似的配置选项。

解决方法在 @shikijs/colorized-bracketsNuxt Content Playground 里找到。你要不说我还真不知道有这个神奇小文件,在 https://github.com/nuxt-content/mdc/issues/124 被提出然后又在 https://github.com/nuxt-content/mdc/pull/129 被默默实现,我没招了

不过得知了这个配置文件后新的问题出现了:配置文件不生效。检查了一番后发现是要放在 srcDir 下,在我的博客项目里也就是 app 😅 这是 MDC 对应的代码

好了,现在我们成功添加了自定义的 Shiki Transformer。欸,打开开发服务器一看,怎么代码块的空格没有可视化?!

Debug 了十几分钟后发现解析逻辑在 这里。原来还有缓存机制,也就是说如果只修改了 mdc.config.ts 文件,由于文件内容没有改变所以 Nuxt Content 并不会重新解析对应的 markdown 文件,从而导致自定义的 transformer 不生效。

之前我的文章 frontmatter 里有一些嵌套的对象结构,比如:

---
repost:
  source_only: true
---

这是代表一篇转载的文章是否应当跳转到原文链接阅读。而我不想让这些转载的文章出现在上一篇/下一篇的导航中,需要使用 where 查询把它们过滤掉。很自然地写出了这样的代码:

我想查询某个 author 的文章,自然就写了这样的代码:

queryCollectionItemSurroundings("posts", route.path).where(
  "repost.source_only",
  "=",
  false,
);

结果查询不到任何结果。后面翻了 Nuxt Content 源码,发现这个 where 语句是简单的拼接得到最后执行的 SQL 语句,而 SQL 不支持这种点号访问嵌套对象的方式,倒也合理。于是查询了 SQLite 进行这类查询的文档,发现需要使用 JSON 函数来访问嵌套对象,比如 json_extract

SELECT * FROM posts
WHERE json_extract(repost, '$.source_only') = false;

但是 Nuxt Content 把 where 的第一个参数用引号括起来了,所以没法直接写 SQL 函数调用。只能把 repost 的这个属性扁平化了。我先提了一个 issue,希望能得到修复。

之前考虑过要不要做标签功能,后面放弃实现,其中大部分原因是我认为对我而言标签功能好看多于实用;其次则是 Nuxt Content V3 居然把 $contains 操作符给干掉了

也就是说,这样的字段,

---
tags: ["vue", "nuxt", "typescript"]
---

我想查询包含 vue 标签的所有文章,在 V2,应该用 $contains 操作符:

await queryContent("posts")
  .where({ tags: { $contains: "vue" } })
  .find();

但是在 V3 这个功能被移除了。Issue 的回复说用 LIKE 来替代,但这种方式很不直观,而且有局限性:

await queryContent("posts").where("tags", "LIKE", "%vue%").find();

这样写有个问题:如果有个标签叫 vuejs,也会被匹配到。不过这种极端情况倒不是大问题,可以接受。

在重写过程中,还发现了自己之前写的 @vue.ts/complex-types 库的几个问题。

缺少 enforce: "pre" 导致的。在我的测试流程中 @vue.ts/ccomplex-types 插件被放在 @vitejs/plugin-vue 之前执行,表现出来就是插件在正常工作,实际上这是依赖于用户的插件顺序,并不可靠。而在 Nuxt 里 @vitejs/plugin-vue 是默认存在的插件,用户无法控制顺序,我们加入的插件顺序在 @vitejs/plugin-vue 之后,导致插件不生效。

博客开发时,我发现每次打开页面程序都可以正常工作,但是一旦修改文件内容就会报错无法解析复杂类型。用 Nuxt Devtools 的 Inspect 功能查看后发现,HMR 后的文件内容变回了没有经插件处理过的原始内容,百思不得其解。 这个问题调试了挺久,就是各种试、各种 console.log 才找到原因: transform hook 在 HMR 场景下根本没有被调用。也不知道是不是预期行为,总之需要加入 handleHotUpdate hook 来处理 HMR 才能正常运行,直接使唤 Copilot 代写。成品

我的 complex-types 是以 component-meta 为蓝本写的。Nuxt 最近增强了类型定义,把不同运行环境下的代码分割到了不同的 tsconfig 文件中,并在工作目录下的 tsconfig 通过 references 来关联它们。结果发现 complex-types 插件无法处理这种 tsconfig 结构,询问 KazariEX 后得知不支持 references,于是创建了一个新 tsconfig.build.json extend 旧版的 All-in-One tsconfig 来解决这个问题。

总的来说,Nuxt Content 可用但仍然存在不少问题,请酌情使用。但是如果你能成功解决这些问题,那么 MDC 简洁有力的语法能够极大的提升你的写作效率和体验。

希望这篇记录能帮到同样在使用 / 考虑使用 Nuxt Content 的朋友。