跳到主要内容

生活类 Blog 写作工作流

这篇文档是这个仓库里“生活记录类 blog”的长期规范,同时也记录当前整个 blog/ 板块在发布新文章时必须遵守的分支规则。

目标很明确:

  • 让以后“给一组照片链接和一点补充信息,就能稳定产出一篇可发布的生活 blog”这件事可以重复执行
  • 让生活类 blog 和旅行类页面在“图片可点击放大”这件事上保持统一,但在版式上区分开,不混成同一种相册页

如果后续继续写聚餐、小聚、日常散步、看展、去某家店、阶段性生活片段这类内容,建议以这篇文档为准。

如果后续写的是思考随笔、技术感想或 AI 工具体验,这篇文档仍然要用来检查文件结构、frontmatter、博客总览、归档页和构建流程,但正文风格不强行套用生活记录类的写法。

当前 blog 板块结构

当前 blog 板块由 Docusaurus classic blog 插件和两个自定义主题页面共同组成:

  • 文章统一放在 blog/ 目录下
  • 作者信息来自 blog/authors.yml
  • blog 插件配置在 docusaurus.config.jspresets.classic.blog 中,当前启用了 showReadingTime: trueblogSidebarCount: 'ALL'
  • /blog 总览页由 src/theme/BlogListPage/index.jssrc/theme/BlogListPage/styles.module.css 控制
  • /blog/archive 归档页由 src/theme/BlogArchivePage/index.jssrc/theme/BlogArchivePage/styles.module.css 控制
  • 图片灯箱能力来自 src/components/TravelGallery

写新 blog 时,必须先判断文章类型:

类型默认文件默认标签图片要求说明
生活记录图文.mdx[life]必须写 image,正文用 TravelGallery layout="article"聚餐、小聚、日常片段、看展、散步等
纯文字随笔.md[essay]不需要 image,不需要 TravelGallery没有正文图片的思考、复盘、阶段感想
技术 / AI 工具体验.md.mdx[tech]有图才写 image,需要灯箱才用 TravelGallery layout="article"AI 工具、Agent、模型体验、工程实践短记
赛车 / 汽车兴趣内容.md.mdx[motorsport]有图才写 image,需要灯箱才用 TravelGallery layout="article"F1、模拟赛车、性能车、车迷文化
科研 / 学术思考.md.mdx[research]有图才写 image,需要灯箱才用 TravelGallery layout="article"科研范式、学术训练、研究判断力

因此,.mdx 不是整个 blog 板块的唯一正确格式;它是“需要 JSX 组件或图片灯箱”的文章格式。纯文字文章继续使用 .md 是正确的。

当前页面读取逻辑

发布新文章时,需要理解这些页面分别读取什么字段:

  • /blog 总览页读取 titledatedescriptiontagsreadingTime 和封面图
  • /blog 总览页的摘要优先使用 frontmatter 里的 description,不要依赖正文自动截取
  • /blog 总览页的封面图优先使用 frontmatter 里的 image
  • 如果没有 image/blog 总览页才会查 src/theme/BlogListPage/index.js 里的 POST_VISUALS
  • POST_VISUALS 是历史文章的兼容回退,不是新文章的默认发布流程
  • /blog/archive 归档页按文章日期自动分组,读取 titledatereadingTime 和最多两个 tags
  • /blog/archive 不需要单独配置每篇文章,也不读取 descriptionimage
  • 标签页和 blog 侧边栏由 Docusaurus 根据 tags 和 blog 元数据自动生成

也就是说,一篇新文章要想在当前 blog 板块里完整显示,最关键的是:文件放对、日期正确、title 正确、authors 正确、tags 正确、description 独立可读;如果文章有图,还必须写 image

Blog 标签体系

blog 标签采用“少量稳定大类 + 必要时新增”的策略。标签写在文章 frontmatter 的 tags 字段里,统一使用小写英文;显示名和 permalink 由 blog/tags.yml 维护。

当前固定标签如下:

标签显示名适用内容
lifeLife日常、朋友、饭局、看展、散步、生活片段
techTechAI、Agent、工具体验、工程实践、技术趋势
motorsportMotorsportF1、模拟赛车、性能车、车迷文化
researchResearch科研范式、学术训练、研究判断力
essayEssay个人价值观、自我提醒、文学或人生反思

默认只给每篇文章 1 个主标签。文章确实横跨两个稳定主题时,可以使用 2 个标签,例如 AI 生图生成 F1 车手海报可以写 [tech, motorsport],模拟赛车加朋友聚会可以写 [life, motorsport]

不要再使用旧标签 thoughtslife-logthoughts 太泛,容易把技术、兴趣和个人反思混在一起;life-log 已收敛为更短的 life

允许新增合适的标签,但必须满足下面的条件:

  • 新标签代表一个会长期复用的内容方向,而不是单篇文章的临时关键词。
  • 同类内容已经有多篇,或者明确预计后续会持续写。
  • 不能用具体品牌、模型、人名、赛事名做标签,例如不要新增 gpt-image-2bmwverstappen
  • 新标签使用小写英文短词或短横线形式,例如 designreadingproduct-notes
  • 新增标签时必须同步更新 blog/tags.yml,写清 labelpermalink

先理解当前约定

当前仓库里,生活记录类图文 blog 的默认技术实现是:

  1. 文章放在 blog/ 目录下
  2. 文件格式默认使用 .mdx,不是普通 .md
  3. 图片默认复用 src/components/TravelGallery 的灯箱能力
  4. 但图片排版不使用旅行页那种瀑布/拼图布局,而是使用文章模式 layout="article"
  5. 文章模式下,图片按原始宽高比显示,不裁切、不强行限高,只在正文宽度内自适应
  6. 图片点击后仍然可以放大查看

这意味着:

  • 生活类 blog 默认是“图文文章”
  • 不是“旅行相册”
  • 不是“纯文字散文”
  • 也不是“只插几张普通 Markdown 图片”

当前可直接参考的成稿样例是:

  • blog/2026-04-18-friends-meet-firepot-bbq.mdx

注意:部分历史文章可能还依赖 /blog 总览页里的 POST_VISUALS 映射来显示封面。以后新增文章不要把这种回退当成默认做法;只要文章有图,就直接在 frontmatter 写 image

生活类 blog 的默认定位

生活类 blog 的核心不是攻略、评测或者流水账,而是把一个值得留下来的片段写清楚。

默认写法是:

  • 以场景和氛围为主,不以打分或推荐为主
  • 以“我和朋友们/我当时的感受”为主,不以完整复盘事件经过为主
  • 以真实细节为主,不强行拔高,不硬写成抒情散文
  • 以可发布为目标,不写太私人、太识别化的信息

一句话概括:

生活类 blog 默认写成“温暖纪实 + 一点感受 + 少量有用细节”。

默认写作风格

如果没有额外说明,统一按下面这些默认值执行:

  • 语气:温暖纪实
  • 视角:第一人称
  • 隐私级别:克制泛写,不点朋友姓名,不给出可识别身份信息
  • 文章长度:通常 800 到 1600 字
  • 文章结构:2 到 4 个小节,不写得太散
  • 文章目标:记录一个晚上、一顿饭、一次见面、一次日常体验
  • 文风边界:可以有情绪,但不要故作深沉;可以有细节,但不要写成菜单堆砌

默认不做的事:

  • 不把文章写成店铺测评
  • 不写“推荐指数”“人均”“环境几星”这类内容,除非你明确要求
  • 不虚构对话、人物反应和未发生的情节
  • 不为了文采牺牲真实感

什么时候适合写生活类 blog

下面这些场景都适合按这套流程写:

  • 和朋友小聚、聚餐、烧烤、火锅、夜宵
  • 去了某家很有记忆点的小店
  • 某个普通但很舒服的晚上
  • 一次短暂散步、看展、逛街、喝酒、聊天
  • 某个时间点很想记一下的生活片段

如果内容明显更偏“旅行线路、城市风景、照片为主”,那还是优先归到 docs/Travel,不要混到生活类 blog 里。

以后给 AI 的素材格式

后续如果你想继续沿用这套流程,建议直接按下面这份模板给素材。

最少需要这些内容:

- 事件:
- 日期:
- 地点 / 店名:
- 照片链接:
- 明确的菜品 / 物品 / 场景:
- 明确的酒水 / 饮料:
- 我想强调的细节:

推荐补充这些内容:

- 想要的语气:温暖纪实 / 轻松有趣 / 更文艺一点
- 隐私要求:只写朋友们 / 可以写更多个人感受
- 是否加入知识内容:不要 / 少量 / 单独一小节
- 是否需要提交 git:是 / 否

最推荐的完整输入模板如下:

- 事件:和朋友们烧烤小聚
- 日期:2026-04-18
- 地点 / 店名:川西火焰山西昌火盆烧烤(新都格林城市花园店)
- 照片链接:
- https://...
- https://...
- 明确菜品:
- 跑山小香猪
- 麻辣嫩牛肉
- 香烤五花肉
- 明确酒水:
- 汾酒
- 朝日啤酒
- 想强调的细节:
- 调料盘里有黄豆粉
- 第一次蘸黄豆粉吃烧烤,十分惊艳
- 想要的语气:温暖纪实
- 隐私要求:克制泛写
- 是否加入知识内容:单独一小节

给 AI 看图时的默认规则

如果任务包含“先看照片,再写文章”,默认按下面执行:

  • 不直接读取原图,先生成压缩预览图,再让 AI 阅读
  • 直接读取原图很容易触发对话上下文大小限制,经验风险线约在 20MB 左右
  • 默认第一档预览图使用长边 1280px
  • 如果 1280px 仍看不清小字、菜单、包装或关键细节,才升到第二档长边 1920px
  • 如果一轮要看很多图,先分批压缩、分批阅读,不要一次把整组原图塞进对话
  • 原图保留给发布、人工核对和必要时的细节复查,不作为默认读图输入

图片处理规则

1. 生活类图文 blog 默认使用 MDX

原因很简单:普通 Markdown 图片不方便复用现有的点击放大能力。

所以默认使用:

  • blog/*.mdx

而不是:

  • blog/*.md

这条只约束生活记录图文文章。纯文字随笔没有 JSX 组件需求,继续使用 blog/*.md;带图随笔如果要复用 TravelGallery,则使用 blog/*.mdx

2. 默认使用 TravelGallery,但必须用文章模式

生活类 blog 默认图片写法:

import TravelGallery from '@site/src/components/TravelGallery';

然后使用:

<TravelGallery images={photos} layout="article" />

这里有两条必须记住:

  • 生活类 blog 默认 layout="article"
  • 不使用旅行板块的瀑布/拼图布局,除非我明确要求“这篇就做成相册感”

3. 图片按原始比例显示

生活类 blog 中的图片默认行为是:

  • 不裁切
  • 不做固定高度展示框
  • 不做旅游相册那种拼接对齐
  • 按原始宽高比显示
  • 只在正文宽度内自适应缩放

这条是固定默认值,后续不要再改回瀑布式逻辑,除非我明确要求。

4. 图片数量不多时,优先按“场景组”来放

推荐不是把所有图塞成一个大相册,而是按场景拆成 2 到 4 组。

例如:

  • 第一组:桌面、炭火、肉串
  • 第二组:倒酒、小杯、调料盘
  • 第三组:主菜、鱼盘、配菜

这样比“一整坨图片堆在一起”更像文章。

5. 每张图尽量有 altcaption

推荐写法:

export const photos = [
{
src: 'https://oss.nevergpdzy.com/xxx.jpg',
alt: '朋友倒酒',
caption: '小杯里倒上汾酒,桌边已经有了朋友局该有的松弛感。',
},
];

默认要求:

  • alt 说清图里是什么
  • caption 用一句短话补足那张图的情绪或信息
  • 不要把 caption 写成长段正文

6. 有图文章必须写 image 作为 Blog 总览封面

当前 /blog 总览页的封面图读取逻辑在 src/theme/BlogListPage/index.js 里:

  • 优先读取文章 frontmatter 里的 image
  • 如果没有 image,才读取总览组件里的手写 POST_VISUALS 映射
  • 如果两边都没有,这篇文章在总览页就不会展示封面图,右侧会空出来

所以以后只要文章里有图片,frontmatter 必须写:

image: https://oss.nevergpdzy.com/xxx.jpg

这张图默认选文章里最能代表全文的第一张主图,优先使用自有域名 https://oss.nevergpdzy.com/...,不要使用 OSS 原始域名。

注意,TravelGallery 里的图片只负责正文展示和点击放大,不会自动变成 /blog 总览页封面。封面必须单独写 image。不要为了新文章去新增 POST_VISUALS,除非是在维护历史文章的兼容展示。

文章结构默认模板

如果没有特别要求,生活类 blog 默认使用下面这套结构。

1. 开头:先交代场景

第一段负责交代:

  • 时间
  • 地点
  • 这次是个什么局
  • 为什么值得记一下

这段通常也应该放在 <!--truncate--> 之前,方便博客列表页展示摘要。

不过博客总览页的卡片摘要默认读取 frontmatter 里的 description,不要再依赖正文开头自动截取。开头可以自然进入场景,摘要则单独写成一句概括。

2. 中段:写场景和食物,但不要写成点单清单

中段的重点不是把所有菜报一遍,而是:

  • 抓住最有画面的场景
  • 抓住最有记忆点的吃法
  • 抓住一两个真正值得写的细节

比如:

  • 炭火慢慢把肉烤出焦香
  • 桌上有人看火、有人翻面、有人倒酒
  • 第一次尝试某种吃法,结果很惊艳

如果用户已经明确给了完整菜品列表,可以在正文中自然带进去,但不要生硬罗列。

3. 知识内容:可加,但必须克制

如果你明确要求“加一点知识进去”,默认做法是:

  • 单独开一个短小节
  • 只写和这顿饭直接相关的知识
  • 占比不要压过整篇生活记录

最常见的是:

  • 某种酒的风格、背景、口感路线
  • 某种食材或吃法的小知识

默认不要做成:

  • 百度百科式长科普
  • 大段品牌史
  • 一堆来源堆砌到正文里

知识段的作用是“增加一点层次”,不是抢走生活记录本身。

4. 结尾:回到“为什么值得记一下”

结尾默认收回到人和场景本身。

比较合适的落点通常是:

  • 这一晚真正留下来的是什么
  • 让人记住的不只是吃了什么,还包括什么氛围
  • 一个普通夜晚为什么仍然值得记录

默认不建议在结尾硬上价值,也不建议强行写成鸡汤。

内容判断优先级

以后生成文章时,信息使用顺序固定如下:

  1. 你明确口头给出的信息
  2. 你给的文字补充
  3. 从图片中能稳定识别出的内容
  4. 最后才是谨慎推断

也就是说:

  • 如果你明确说酒是汾酒,那就按汾酒写
  • 如果图片看不清品牌,但你口头确认了,就以你的确认为准
  • 如果图片只能模糊看出“像某种肉串”,而你没确认,就不要写得太死

文件命名与 frontmatter 规范

默认命名:

  • blog/YYYY-MM-DD-short-slug.mdx

例如:

  • blog/2026-04-18-friends-meet-firepot-bbq.mdx

日期规则必须按文章记录的事件日期或素材日期来定,而不是按写作当天随手取当前日期。

如果用户使用相对日期,要先换算成绝对日期再命名。例如当前日期是 2026-04-26,用户说“昨天生成的海报”,文件名就应该使用 2026-04-25-...mdx,正文里的日期也要写成 4 月 25 日

默认 frontmatter:

---
title: 一篇短标题
authors: DingZhiyu
tags: [life]
description: 一句独立摘要,概括这篇记录真正写了什么。
image: https://oss.nevergpdzy.com/xxx.jpg
---

默认规则:

  • title 用中文短标题,不要太长
  • authors 固定为 DingZhiyu
  • tags 根据文章主题从当前 blog 标签体系中选择,生活记录默认使用 [life]
  • description 必须单独写,不直接复制正文开头,不写“事情是这样的”这类泛开场
  • description 建议 35 到 70 个中文字符,说明场景、记忆点和文章价值,供 /blog 总览卡片展示
  • 只要文章有配图,就必须写 image,供 /blog 总览页和最新文章卡片展示封面
  • image 默认使用文章第一组图片里最有代表性的主图,并且必须使用自有域名

如果明显更偏思考随笔,而不是生活记录,优先改成 [essay];如果文章主线是 AI、Agent、工具体验或工程实践,优先使用 [tech];如果主线是赛车或汽车兴趣,优先使用 [motorsport]

纯文字随笔可以使用下面这个更轻的 frontmatter,不需要 image

---
title: 一篇短标题
authors: DingZhiyu
tags: [essay]
description: 一句独立摘要,概括这篇随笔真正讨论了什么。
---

如果随笔正文有图片并需要出现在 /blog 总览页封面,仍然必须使用 .mdx 并补上 image

生活类 blog 的固定技术模板

以后默认按下面这个骨架来组织:

---
title: 标题
authors: DingZhiyu
tags: [life]
description: 一句独立摘要,概括这篇记录真正写了什么。
image: https://oss.nevergpdzy.com/xxx.jpg
---

import TravelGallery from '@site/src/components/TravelGallery';

export const scenePhotos = [
{
src: 'https://oss.nevergpdzy.com/xxx.jpg',
alt: '图片说明',
caption: '一句简短图注。',
},
];

# 标题

开头第一段。

<!--truncate-->

正文第二段。

<TravelGallery images={scenePhotos} layout="article" />

后续正文。

如果一篇文章里有多组图,就继续定义:

  • foodPhotos
  • drinkPhotos
  • detailPhotos

这样的变量名即可。

博客音频 (TTS)

每篇博客文章都配有 TTS 朗读音频。音频由独立项目 tts-blog-generator(位于项目父目录 D:\Code\tts-blog-generator\)生成,托管在阿里云 OSS。

音频播放器自动挂载

博客音频播放器通过 src/theme/BlogPostPage/index.js(Docusaurus 主题 swizzle)自动注入到博客文章页面布局中,无需在每篇 MDX/MD 文件里手动引入组件。

自动挂载逻辑:

  1. getBlogAudioSlug(metadata)metadata.permalink 的最后一段推导出 slug
  2. 将推导出的 slug 传给 <BlogAudioPlayer slug={audioSlug} />
  3. 播放器组件内部查找 blogAudioManifest.json,如果有对应条目则渲染播放器,没有则返回 null

关键文件:

文件作用
src/theme/BlogPostPage/index.js博客页面布局 swizzle,自动注入播放器
src/utils/blogAudio.jsgetBlogAudioSlug() 从 permalink 推导 slug
src/components/BlogAudioPlayer/index.js核心播放器组件

slug 推导规则:metadata.permalink 取最后一段路径,等价于文件名去掉日期前缀和扩展名。

文件名permalinkslug
2026-04-27-bmw-m3-touring-24h-note.mdx/blog/2026/04/27/bmw-m3-touring-24h-notebmw-m3-touring-24h-note
2024-6-9-The_method_of_spiritual_victory.md/blog/2024/6/9/The_method_of_spiritual_victoryThe_method_of_spiritual_victory

播放器特性:

  • 自动根据当前 locale(zh-Hans / en)查找对应语言的音频
  • 播放/暂停、进度条拖拽、倍速控制(0.75x / 1.0x / 1.25x / 1.5x / 2.0x)
  • 多段音频预加载,无缝衔接
  • 无音频时组件返回 null,不渲染任何 DOM

生成中文音频

cd ../tts-blog-generator
python generate.py

生成英文音频

python generate.py --lang en --blog-dir “../Dev-Knowledge-Base/i18n/en/docusaurus-plugin-content-blog”

复制清单文件到项目

生成完成后,将清单文件复制到项目中:

cp output/blog-manifest.json ../Dev-Knowledge-Base/src/data/blogAudioManifest.json
cp output/blog-manifest.json ../Dev-Knowledge-Base/static/audio/blog/manifest.json

清单文件 blogAudioManifest.json 是播放器组件的数据源,通过静态 import 引入。中文条目 key 为 slug(如 agent-harness),英文条目 key 为 en/ + slug(如 en/agent-harness)。 不要复制 output/manifest.json;它是博客和文档的总清单,可能混入 docs 音频条目。

音频生成的技术细节

  • TTS API 使用 MiMo-V2.5-TTS,中文语音为「茉莉」,英文语音为「Chloe」
  • API 单次请求有 ~3500 字符限制,长文章自动按段落边界分块
  • 生成的 WAV 文件通过 ffmpeg 转换为 MP3
  • MP3 文件上传到 OSS 的 Audio/blog/(中文)和 Audio/blog/en/(英文)目录
  • 生成器是幂等的:已存在的 MP3 文件会被跳过,使用 --force 可强制重新生成

OSS 防盗链配置

音频托管在 oss.nevergpdzy.com,需在阿里云 OSS 防盗链白名单中添加:

  • https://nevergpdzy.cn(生产环境)
  • http://localhost:3000(本地开发)

同时开启「允许空 Referer」。

发布前检查清单

每次写完以后,至少检查下面这些点:

  • 文件是否放在 blog/
  • 文章类型是否判断正确:生活记录图文、纯文字随笔、带图随笔不要混用规则
  • 如果是生活记录图文或带图随笔,是否使用 .mdx
  • 如果是纯文字随笔,是否确认 .md 即可,不强行引入图片组件
  • 如果正文需要图片灯箱,是否正确引入 TravelGallery
  • 如果使用 TravelGallery,是否明确使用了 layout=”article”
  • 有图文章是否写了 frontmatter image
  • image 是否使用自有域名,并且能作为 /blog 总览页封面
  • 图片是否按原比例显示
  • 图片是否可点击放大
  • titleauthorstags 是否正确
  • 如果新增了标签,是否同步更新 blog/tags.yml
  • description 是否是独立摘要,而不是正文第一句或泛开场
  • <!--truncate--> 是否放在合适位置
  • /blog 总览页是否能正确显示标题、摘要、标签、日期、阅读时间和封面或纯文字卡片
  • /blog/archive 是否能按正确年份和日期列出文章
  • 对应标签页是否能通过 tags 自动收录文章
  • 文件名日期、正文日期是否和用户给出的事件日期一致
  • 如果用户说”昨天””前天”这类相对日期,是否已经按当前日期换算成绝对日期
  • 是否误写了菜品、酒名、店名、日期
  • 是否暴露了不该写的隐私
  • 如果用户要求了知识内容,是否控制在合理篇幅内
  • 是否运行了 TTS 生成脚本(中文 python generate.py,英文 python generate.py --lang en ...
  • 清单文件 blogAudioManifest.json 是否已复制到 src/data/static/audio/blog/
  • 最后是否执行 npm run build

如果用户要求版本更新和提交 git

如果任务不只是写文章,还包括版本更新和提交代码,统一按下面执行:

  1. 先读 VERSIONING.md
  2. 判断应该升 patchminor 还是 major
  3. 运行对应的版本命令
  4. 确认 package.jsonpackage-lock.json 都已更新
  5. 执行 npm run build
  6. 再提交 git

默认理解:

  • 单纯修错字:PATCH
  • 新增一篇生活类 blog,或者新增一个非破坏性的可见功能:MINOR
  • 破坏路由或大改结构:MAJOR

以后执行这类任务时的默认结论

如果我以后只给一组生活照片和一点补充信息,而没有说太多额外要求,那么默认按下面执行:

  • 输出到 blog/
  • 先判断文章类型:生活记录图文默认走 .mdx,纯文字随笔可用 .md,带图随笔用 .mdx
  • 需要图片灯箱时才用 TravelGallery
  • 使用 TravelGallery 时强制 layout="article"
  • 图片按原比例显示
  • frontmatter 里写独立的 description,用于博客总览卡片
  • 有图文章必须在 frontmatter 里写 image,用于 /blog 总览页封面
  • image 和正文图片默认使用自有域名 https://oss.nevergpdzy.com/...
  • 文件名日期使用事件日期或素材日期,不用写作当天日期;相对日期必须先换算成绝对日期
  • 如果需要看图,先读压缩预览图;默认长边 1280px,必要时升到 1920px,不直接读取原图
  • 文章语气为温暖纪实
  • 隐私写法为克制泛写
  • 结构按“开头场景 -> 中段细节 -> 可选知识小节 -> 结尾收束”
  • 如果有特别鲜明的细节,就把它写成整篇文章的记忆点
  • 如果我要求“加一点知识”,就单独开短小节,不喧宾夺主
  • 最后跑 npm run build

这份文档的目的不是把文章写死,而是把默认决策写清楚。以后大部分生活类 blog,可以直接按这份规范落地,不需要每次从头重新定流程。