这篇写法指南把所有规则列清楚,照着来就行。

扩展语法

博客支持 博客写作语法指南 中列出的所有扩展语法,包括 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徽标适用
essayESSAY观点、方法论、判断。有立场,不一定有操作
guideGUIDE教程。别人照着做能得到同样结果
case-studyCASE STUDY案例复盘。我做过一件事,过程、坑、结果都写上
reviewREVIEW产品 / 工具评测,含长期使用体验
logLOG记录。版本更新、项目日志、阶段性存档
noteNOTE笔记、随想、读书与游戏随感
referenceREFERENCE长期维护的资料。会反复改,比如本页

判断不清时问自己一个问题:读者能照着做吗? 能,就是 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,同一系列文章保持一致
  • 侧边栏会自动生成系列导航,上一篇 / 下一篇

现有七部书系:

seriesseriesId装的是什么
从零开始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),两到三篇。结尾会以「继续阅读」呈现,鼠标悬停时右侧显示「读这篇 →」。

这是唯一一个系统会优先采信人工判断的地方。 优先级是:

  1. readNext 人工指定
  2. 同系列下一篇
  3. 按发布时间的前一篇 / 后一篇(兜底)

第 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 做三件事:

  1. 生成同名 .webp(页面默认加载它,留在原引用位置);
  2. 同目录保留一张同名 .jpg 灯箱源(最长边 ≤2560、q82 压缩)——供「点开看原图」时切换;
  3. 未压缩的原始大图归档到 public/.backup_images/(不随站部署、不入库)。

所以磁盘上同名前缀会是三份关系:your-image.webp(显示)+ your-image.jpg(点开放大的原图)+ .backup_images/ 里的原始底片(留档)。

引用方式

![图片描述](/img/your-image.webp)

注意:不要用 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` 构建项目

图片

![图片描述](/img/example.webp)

链接

[链接文字](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 写清楚即可。

发布流程

写完文章后:

  1. 新建走脚本:npm run new "标题" --category 技术 --type guide——它会建到 分类/年份/ 下,文件名自动生成 slug
  2. 图片放进内容仓库的 img/,正文用 ![描述](/img/xxx.webp) 引用
  3. (可选)设置 growth 状态,不填默认为 seed
  4. (可选)设置 planted 日期,不填默认为 pubDate
  5. 填 type,并尽量补上 tldr 和 readNext——这两项决定读者会不会读第二篇
  6. 提交前跑一遍 frontmatter 校验,它能一次定位到 YAML 写坏的文件:
    node scripts/validate-frontmatter.mjs
  7. 在内容仓库里提交推送:
    git add . && git commit -m "content: 新增《标题》" && git push
    → 内容仓库的 Action 会自动在源码仓库创建一个提交 → Cloudflare Pages 重新构建部署
  8. 草稿要靠未来的 pubDate(npm run new --draft 就是干这个的,默认 +60 天)。draft: true 这个字段 schema 里没有、页面也不读它,写了等于没写
  9. changelog.json 只在主题/功能变更时更新,发表文章不用动

改过 frontmatter 之后如果页面还是旧的,先 rm -rf .astro 再构建——Astro 内容层会缓存旧渲染结果,不删缓存看不出改动。

新文章的三条关系

每篇新文章至少建立三条关系:一条前置(这篇为什么会出现)、一条后续(读完能接什么)、一条横向(还跟什么相关)。做到了就顺手填进 readNext,做不到至少在正文里用 [[双向链接]] 连一篇。这个规矩坚持到第 200 篇,整个站会是一张能走通的图,而不是一个列表。

继续阅读Continue Reading

关于本文About

评论Comments

留下你的想法

欢迎在下方评论区留下你的看法,一起把话题聊得更深。

COMMENTS