这篇写法指南把所有规则列清楚,照着来就行。
扩展语法
博客支持 博客写作语法指南 中列出的所有扩展语法,包括 Callout 容器、文本高亮、 Emoji 快捷输入、脚注等。建议写作前先浏览一遍,找找有没有能提升表达效率的语法。
文件与目录
文章和图片现在住在独立的内容仓库 blog-Content(私有)里。源码仓库 CloakBlog 只负责代码,构建时用 Token 把内容仓库拉取到 src/content/blog。写文章改的是内容仓库,不是源码仓库。
文章按分类 / 年份两级归档,目录名必须与 frontmatter 里的 category 一致:
blog-Content/
├── 技术/2026/your-post-name.md
├── 随笔/2025/another-post.md
└── img/ ← 图片源文件文件名就是文章 URL 的 slug(/blog/<文件名>),与它所在的目录无关——构建时会把路径展平。规则:
- 全小写英文字母
- 单词之间用短横线
-连接 - 不要中文、空格、下划线,也不要日期前缀
- 长度控制在 60 字符以内
示例:
✅ 技术/2026/adding-search-to-blog.md
✅ 技术/2026/blog-performance-optimization.md
❌ 随笔/2026/搜索功能.md (文件名不能用中文)
❌ 随笔/2026/2026-09-11-my-post.md (不要日期前缀)
❌ 随笔/2026/blog_performance.md (不要下划线)用 npm run new 新建就不用手动建目录、想文件名:
npm run new "文章标题" --category 技术 --type guide
# → 技术/2026/wen-zhang-biao-ti.md中文标题自动转拼音,分类不在白名单里脚本会拦下来。
归档到子目录不会改 URL
文章下沉到「分类/年份」之后,content.config.ts 的 generateId 仍然只取文件名当 id,所以 /blog/your-post-name 这类旧链接照常有效。
这套仓库结构为什么这么拆、撞过哪些坑,写在 内容仓库改造记 里。
Frontmatter
每篇文章开头必须有一段 YAML frontmatter,用 --- 包裹:
---
title: '文章标题'
description: '一两句话概括文章内容'
pubDate: 2026-06-10
planted: 2026-06-10
category: '技术'
tags: ['Astro', '性能优化']
growth: bloom
type: 'case-study'
---只有前三个字段是必须的,其余全部可选。想让文章进入阅读路径系统(TL;DR / 前置后续 / 继续阅读),再补 type、tldr、prerequisite、followUp、readNext 这几个,见下方阅读路径字段。
字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
title | ✅ | 文章标题,引号包裹;也是双向链接的匹配依据 |
description | ✅ | 一两句话摘要,用于 SEO 和列表预览 |
pubDate | ✅ | 发布日期,格式 YYYY-MM-DD |
planted | ❌ | 播种日期,格式同 pubDate;不填则默认等于 pubDate |
category | ❌ | 分类,不填默认为”技术分享” |
tags | ❌ | 标签数组,不填默认为空 |
updatedDate | ❌ | 更新日期,修改已有文章时添加 |
heroImage | ❌ | 封面图路径,建议使用 public/ 下的 WebP 图片 |
pinned | ❌ | 是否置顶,默认 false |
featured | ❌ | 是否进入首页「精选」区块,默认 false;置 true 即可,不再需要手写 slug 白名单 |
series | ❌ | 系列文集名称 |
seriesOrder | ❌ | 系列内排序编号 |
seriesId | ❌ | 系列唯一 ID,用于侧边栏锚点跳转 |
growth | ❌ | 生长状态:seed(种子)/ bud(发芽)/ bloom(绽放),默认 seed |
aliases | ❌ | 别名数组,与 [[原标题|显示别名]] 语法二选一,效果相同 |
type | ❌ | 内容类型七类之一,显示在文章编号行,见内容类型 |
tldr | ❌ | 三到五行结论,正文前以左色条块展示 |
prerequisite | ❌ | 前置文章 slug,显示在正文前「阅读路径 · 前置」;不填则自动取同系列上一篇 |
followUp | ❌ | 后续文章 slug,显示在正文前「阅读路径 · 后续」;不填则自动取同系列下一篇 |
readNext | ❌ | 人工指定的继续阅读,填文章 slug 数组 |
postscript | ❌ | 发布之后的补充,正文后以虚线卡片展示 |
字段位置
tldr 是多行文本字段,写在 frontmatter 里必须缩进对齐。批量改 frontmatter 时注意别把新字段插进某个缩进列表(比如 aliases)的中间,否则 YAML 解析会直接失败。改完跑一遍 node scripts/validate-frontmatter.mjs,它会逐篇解析并报出出问题的文件名。
分类
分类同时是内容仓库的一级目录名,目前只有六个:
| 分类 | 用途 | 目录 |
|---|---|---|
技术 | 编程、开发、技术实践 | 技术/<年份>/ |
教程 | 手把手教学、指南 | 教程/<年份>/ |
思考 | 观点、方法论、杂谈 | 思考/<年份>/ |
游戏 | 游戏相关 | 游戏/<年份>/ |
随笔 | 随想、记录 | 随笔/<年份>/ |
工具 | 效率工具、软件与服务推荐 | 工具/<年份>/ |
想新开分类,npm run new 会拦住你,需要显式加 --allow-new-category。分类一散,导航和归档页就开始难用,先想清楚再加。
内容类型 TYPE
type 回答的是「这是一篇什么」,标签回答的是「这篇讲什么」。两者不冲突,作用不同:读者在点开之前,先看类型决定要不要读,再看标签判断是不是自己关心的主题。
七种类型,编号行会显示成英文徽标:
| type | 徽标 | 适用 |
|---|---|---|
essay | ESSAY | 观点、方法论、判断。有立场,不一定有操作 |
guide | GUIDE | 教程。别人照着做能得到同样结果 |
case-study | CASE STUDY | 案例复盘。我做过一件事,过程、坑、结果都写上 |
review | REVIEW | 产品 / 工具评测,含长期使用体验 |
log | LOG | 记录。版本更新、项目日志、阶段性存档 |
note | NOTE | 笔记、随想、读书与游戏随感 |
reference | REFERENCE | 长期维护的资料。会反复改,比如本页 |
判断不清时问自己一个问题:读者能照着做吗? 能,就是 guide;不能但能看到完整过程,是 case-study;只有结论没有过程,是 essay。
本站当前分布
教程 39 · 案例 23 · 观点 22 · 资料 12 · 评测 11 · 记录 7 · 笔记 7。教程占三分之一——回头看,这个站有相当一部分内容本质上是「我踩完坑把过程写下来」,本来就带教程性质。
系列文集
同一主题多篇文章可以归为一个系列,方便读者按顺序阅读:
---
title: 'Astro 博客搭建指南'
series: '云图札记搭建指南'
seriesOrder: 1
seriesId: yuntuzhaji-guide
---series:系列名称,同一系列文章填相同值seriesOrder:数字越小越靠前,不填则按发布日期排列seriesId:系列唯一 ID,建议用 kebab-case,同一系列文章保持一致- 侧边栏会自动生成系列导航,上一篇 / 下一篇
现有七部书系:
| series | seriesId | 装的是什么 |
|---|---|---|
| 从零开始 | from-scratch | 从零起步的建站全过程与踩坑记录 |
| 云图札记使用指南 | yuntuzhaji-guide | 本站的写作指南、使用与发布规范 |
| 博客手记 | blog-notes | 博客运营、写作心得与思考随笔 |
| 个人出版物 | personal-publication | 把博客当长期出版物来做:内容系统、构建链路、编辑判断 |
| AI 开发工作流 | ai-workflow | 从选型到落地,一个人怎么和 AI 一起把项目做完 |
| 选择与放弃 | on-choosing | 我为什么选了它,又为什么放弃了另一些 |
| 欧陆风云系列 | eu4-series | 欧陆风云游戏攻略、历史背景与机制解析 |
新开书系要在 src/utils.ts 的 SERIES_META 和 SERIES_ORDER 里登记,否则索引页不会收录(两处都要加)。开新书系前三思——书系的价值在于收束已有文章,不是为了给新文章找地方放。
生长状态(数字园丁)
文章有三种生长状态,体现写作的成熟度:
| 状态 | 标识 | 含义 |
|---|---|---|
seed | 🌱 种子 | 初稿或思路草稿,尚未完成 |
bud | 🌿 发芽 | 已有基本内容,仍在持续完善 |
bloom | 🌸 绽放 | 内容成熟定稿,结构完整 |
不填 growth 字段时默认为 seed。
手动指定示例:
growth: bloom # 文章已完成定稿也可以通过运行脚本自动分配:
node scripts/assign-growth.cjs分配规则:首发超过 180 天 / 被置顶 / 属于系列 → bloom;其余按时间自动判断。
阅读路径:TL;DR / 前置后续 / 继续阅读 / 后记
上面那些字段管的是「文章是什么」,这几个字段管的是「读者怎么走」。它们解决同一个问题:读完一篇之后,为什么该读下一篇。
完整写法长这样:
---
title: '文章标题'
type: 'case-study'
tldr: |
第一句给结论,不铺垫。
第二句说清这篇解决什么问题。
第三句写我的最终判断是什么。
prerequisite: 'slug-of-previous'
followUp: 'slug-of-next'
readNext: ['slug-a', 'slug-b', 'slug-c']
postscript: |
发布之后又发生了什么,补在这里。
---TL;DR
tldr 是正文之前的那块左色条,三到五行,说两件事:这篇解决什么问题,我的结论是什么。
不要写成要点罗列(1. xxx 2. xxx),那和目录重复了。写成人话,一段到底。参考写法见 AI 编程工具选型,别问哪个最好,问哪个最适合你的场景 开头。
一条硬标准:把 TL;DR 单独抽出来读,能不能看懂这篇在讲什么、作者站哪边。 看不懂就重写。
阅读路径(前置 / 后续)
正文之前会有一小块「阅读路径」,只显示三行:前置 → 当前 → 后续。它回答的是这篇文章在一条链里的位置,而不是「本文有哪些章节」。
两个字段都填 slug(文件名去掉 .md),都可以不填:
prerequisite:前置文章。不填则自动取同系列里seriesOrder排在前面的那篇。followUp:后续文章。不填则自动取同系列里的下一篇。
同一系列的文章会自动串成链,大多数时候你不用管;只有跨系列、或者你想起「总纲」作用时,才需要手写。
prerequisite: 'how-i-build-a-personal-publication'
followUp: 'cloakblog-240-reading-paths'系列第一篇不填 prerequisite、最后一篇不填 followUp,块会自动少一行;两篇都没有(比如不属于任何系列)就整块不显示——所以它天然不是每篇必有。
不要再写「阅读路线」
正文内部的章节导航只由侧边栏目录承担。以前那种在正文前再列一遍 H2 的「阅读路线」已经取消——它和目录是同一件事,重复。要表达文章之间的关系,用 prerequisite / followUp。
继续阅读
readNext 填文章 slug 数组(就是文件名去掉 .md),两到三篇。结尾会以「继续阅读」呈现,鼠标悬停时右侧显示「读这篇 →」。
这是唯一一个系统会优先采信人工判断的地方。 优先级是:
readNext人工指定- 同系列下一篇
- 按发布时间的前一篇 / 后一篇(兜底)
第 3 档基本没用——时间上相邻不等于内容上相关。所以值得花三十秒手写 readNext。
顶部「阅读路径」已经出现过的文章,底部不会再重复一遍;只有 readNext 里有、而路径里没有的,才会补在这里。
选哪几篇?按这三条各来一条最好:
- 前置:这篇没展开、但读者可能想先了解的背景
- 后续:读完这篇自然产生的下一个问题
- 横向:同一主题下另一条路
slug 写错不会报错,会被静默忽略,所以填完记得看一眼页面有没有渲染出来。
后记
postscript 是正文之后、版权信息之前那张虚线卡片。用来放发布之后的新进展、修正、或者”当时判断错了”的补充。
配合 growth 用:文章状态还停在 seed 或 bud 时,后记是它继续生长的地方,不用动正文结构。
双向链接
在正文中用 [[文章标题]] 引用其他文章,被引用的文章左侧目录栏底部会出现「根系」板块,展示哪些文章引用了它:
这篇和[[Obsidian 使用指南]]有很多关联……标题太长?用别名语法:
这篇和[[Obsidian 使用指南|Ob指南]]有很多关联……格式为 [[原标题|显示别名]]:
- 原标题:用于匹配目标文章(精确匹配 frontmatter
title),也是反向链接的依据 - 显示别名:渲染后链接上显示的文字,可以比原标题短得多
- 不带
|时,显示文字等于原标题
aliases 字段不可靠
frontmatter 里的 aliases 数组目前不生效——索引里有这个字段,但链接解析匹配不到,清了缓存也不行。实测结论:想用别名引用一篇文章,统一写 [[完整标题|显示别名]],不要依赖目标文章的 aliases。
匹配不到时链接会标红显示为 [[原文]] 的形态,构建不会报错。所以发布前用 node scripts/check-links.mjs(包含在 npm run build 里)扫一遍,或者直接看页面上有没有标红的链接。
被引用的文章左侧目录栏底部的「根系」板块会自动出现当前文章。
示例:
| 你在写的文章 | 内容 | 渲染结果 |
|---|---|---|
my-post.md | 这篇和[[Obsidian 使用指南]]有关联 | 链接文字 = “Obsidian 使用指南” |
my-post.md | `这篇和[[Obsidian 使用指南 | Ob指南]]有关联` |
| 结果 | 根系 | Obsidian 使用指南 左侧目录栏底部出现「my-post」 |
不确定某篇文章的 title 是什么?去首页点击那篇文章,看浏览器标签页标题就是。 如果原标题写错了(匹配不到文章),链接会显示为红色虚线下划线文字,不会跳转到任何页面。
图片规范
放置路径
- 文章配图建议放在
public/img/目录下 - 装备、产品等固定资产图片放在
public/gear/目录下
推荐格式
优先使用 WebP 格式,体积比 PNG/JPG 小 70~90%,画质损失小。
已有转换脚本,添加新图片后运行:
npm run img:convert脚本会自动把 public/ 与 src/assets/ 下的 PNG/JPG/GIF 做三件事:
- 生成同名
.webp(页面默认加载它,留在原引用位置); - 同目录保留一张同名
.jpg灯箱源(最长边 ≤2560、q82 压缩)——供「点开看原图」时切换; - 未压缩的原始大图归档到
public/.backup_images/(不随站部署、不入库)。
所以磁盘上同名前缀会是三份关系:your-image.webp(显示)+ your-image.jpg(点开放大的原图)+ .backup_images/ 里的原始底片(留档)。
引用方式
注意:不要用
src/content/blog/images/目录,该目录不存在。
灯箱放大
文章内的图片点击后会打开灯箱视图,支持:
- 双指捏合缩放
- 拖动平移
- 关闭按钮 / 点击遮罩退出
不需要额外配置,Markdown 图片语法自动生效。
Markdown 写法
正文
直接写 Markdown,不需要特殊 HTML 结构。几个注意事项:
- 段落:空行分段,不要用
<br>硬换行 - 标题:从
##开始,#是文章标题(由 frontmatter 的title生成) - 代码块:用三个反引号 + 语言标识
```javascript
const greeting = 'Hello, World!';
```支持的语法
博客支持以下 GFM(GitHub Flavored Markdown)语法:
表格
| 方案 | 优点 | 缺点 |
|------|------|------|
| 方案A | 快速 | 贵 |
| 方案B | 便宜 | 慢 |任务列表
- [x] 已完成的任务
- [ ] 未完成的任务删除线
~~这段文字会被划掉~~行内代码
用 `pnpm run build` 构建项目图片
链接
[链接文字](https://example.com)支持的 Markdown 扩展语法
除了标准 Markdown,博客还支持以下扩展语法——详见 博客写作语法指南:
文本高亮 ==重点文字== — 黄色背景标记关键词汇,克制使用效果最佳。
Emoji 快捷 :emoji_name: — 比直接输入 Emoji 更好维护,如 :warning: → ⚠️、:bulb: → 💡。
Callout 容器 :::tip\n内容\n::: — 醒目的信息提示框,适合技巧提示、注意事项、核心结论。支持 tip / note / warning / bug / abstract / example / question / success 等类型。
脚注 [^1] + [^1]: 说明 — 参考文献和补充说明,不打断正文阅读流。
不建议写法
以下语法博客不会渲染,避免使用:
$数学公式$(LaTeX,未启用)- HTML 标签(除非确实必要)
写作风格
这是云图札记写作风格,不是硬性规则但建议遵守:
说人话。 写你真正做了什么、想了什么,不要写教科书式定义和原理。读者来看博客是看经验,不是看文档。
具体优先。 “我试了三种方案,A 太慢放弃了,B 报错了,用 C 解决” 比 “经过充分调研和对比分析,最终选定了最优方案” 有用得多。
不要升华。 写到一个事实就停。“这个方案解决了我问题,构建时间从 12 秒降到 3 秒” 就是好结尾。不要加”技术的本质是…”之类的总结。
少用否定平行。 “不是 X,而是 Y” 的句式偶尔用可以,连续三段都是这种写法就过了。直接说 Y 就行。
代码要能跑。 贴的代码片段最好是从实际项目中复制出来,不是随手写伪代码。如果代码太长,贴关键部分,注释说明省略了什么。
更新日志
不要什么内容都去更新 changelog.json。只有当涉及以下变更时才更新:
- 新增页面(about、archive、友链等)
- 主题或功能重构(导航调整、阅读偏好、评论汇总等)
- 显著的设计变更(字体、排版、配色等)
日常发表不需要动 changelog.json,commit message 写清楚即可。
发布流程
写完文章后:
- 新建走脚本:
npm run new "标题" --category 技术 --type guide——它会建到分类/年份/下,文件名自动生成 slug - 图片放进内容仓库的
img/,正文用引用 - (可选)设置
growth状态,不填默认为seed - (可选)设置
planted日期,不填默认为 pubDate - 填
type,并尽量补上tldr和readNext——这两项决定读者会不会读第二篇 - 提交前跑一遍 frontmatter 校验,它能一次定位到 YAML 写坏的文件:
node scripts/validate-frontmatter.mjs - 在内容仓库里提交推送:
→ 内容仓库的 Action 会自动在源码仓库创建一个提交 → Cloudflare Pages 重新构建部署git add . && git commit -m "content: 新增《标题》" && git push - 草稿要靠未来的
pubDate(npm run new --draft就是干这个的,默认 +60 天)。draft: true这个字段 schema 里没有、页面也不读它,写了等于没写 - changelog.json 只在主题/功能变更时更新,发表文章不用动
改过 frontmatter 之后如果页面还是旧的,先 rm -rf .astro 再构建——Astro 内容层会缓存旧渲染结果,不删缓存看不出改动。
新文章的三条关系
每篇新文章至少建立三条关系:一条前置(这篇为什么会出现)、一条后续(读完能接什么)、一条横向(还跟什么相关)。做到了就顺手填进 readNext,做不到至少在正文里用 [[双向链接]] 连一篇。这个规矩坚持到第 200 篇,整个站会是一张能走通的图,而不是一个列表。