装修文章也要有自己的家:XiaoHeiBaiMeow 的内容管道和猫咪交互

XiaoHeiBaiMeow 是一个面向装修内容的个人博客,线上站点是 jiangjiang.zone。它写的是半包辅材、施工工艺、水电验收和装修过程记录,前端则故意带了一点轻松的视觉气质:波浪背景、猫咪加载动画、明暗主题、鼠标粒子……看起来像一只在装修现场到处巡视的猫。

我做它时最不想要的结果,是每写一篇文章就去改一次 React 组件。内容应该待在内容该待的地方,组件负责展示,构建脚本负责把两边接起来。这个想法很普通,但真的把文章目录和页面逻辑分开以后,维护体验会从“每次发布都小心翼翼”变成“放一篇 Markdown,然后看它自己排好队”。(:з」∠)

内容的源头:一个目录,一篇 Markdown

文章放在 public/notes/<文章名>/index.md 里,每篇通过 YAML front matter 描述标题、描述、创建时间、更新时间和标签。正文还是 Markdown,不用在 JSX 里写一大段字符串,也不用为了一个小标题去碰页面组件。

构建时,build-notes.ts 遍历这些目录,读取每个 index.md,解析元数据并去掉 front matter,把正文和索引信息写进 public/notes.json,同时汇总全站标签:

1
2
3
4
5
6
7
8
9
public/notes/*/index.md
↓ YAML + Markdown
build-notes.ts
↓
public/notes.json
├─ notes:文章元数据与正文
└─ tags:去重后的标签及聚合信息
↓
React Query → 首页 / 归档 / 标签 / 文章详情

生成的条目还带封面路径和字符数,文章按更新时间倒序排,标签用 Set 去重。构建脚本会检查重复的文章目录和缺失的元数据,在构建阶段就报错——而不是等站点上线后才发现一篇文章没标题、没标签,或者两个页面互相覆盖。

这一步很像给 Markdown 加了一层小型数据库索引,但它仍然保持文件系统的直观性:打开目录就能找到文章,Git diff 也能清楚看到内容到底改了什么。

前端只请求一份事实

前端的 useNotes Hook 请求生成好的 notes.json,React Query 负责缓存,默认缓存约五分钟,并关掉不必要的后台轮询。首页、归档页、标签云和标签筛选都从同一份数据源计算,文章页根据动态路由加载对应正文。

这样可以避开一个很常见的个人博客问题:首页按一套排序,归档按另一套排序,标签页又手动维护第三份文章列表,最后同一篇文章在三个地方显示三个更新时间。索引文件成为唯一事实来源之后,页面只负责提出不同的视图。

一篇新文章从写完到上线,会经过什么

我希望发布流程尽量接近“放文件”,但放文件不代表没有约定。新增文章时要建一个新目录和 index.md,补齐标题、描述、创建时间、更新时间和标签;有封面的话,图片放同一目录。下一次 pnpm build 会先跑 TypeScript 构建和 build-notes,脚本扫描目录、解析 front matter、算字符数并生成索引。

标题或元数据缺失?构建直接失败。文章目录重复?在生成 notes.json 之前就报错。这样问题会出现在作者刚提交代码的时候,而不是读者打开一个空白文章页之后。静态站点最舒服的地方是部署简单,最容易忽略的地方则是构建脚本悄悄承担了很多内容质量检查。

生成的文章条目要同时服务首页卡片、归档排序、标签聚合和详情页,所以字段命名和时间格式必须稳定。前端组件不应该自己猜“这篇文章到底有没有更新时间”,更不应该为了一个页面把日期格式化成另一种含义。

文章详情用 /note/:title 这样的动态路由,文章卡片、标签计数和归档列表共享同一套元数据。加文章不需要改路由组件,只要遵守目录和 front matter 约定,下一次构建就会把它带进来。

路由、懒加载和静态站点的现实

项目用 React、TypeScript、Vite、React Router 和 Tailwind CSS。首页、关于、归档、文章、标签以及一些“宝藏”实验页面拆成独立路由,主要页面通过 lazy 动态导入。首次访问只加载布局、Header、Sidebar 和当前页面,打开文章或实验工具后才加载相应的渲染逻辑。

布局层组合 Header、侧边栏、正文和 Footer。Sidebar 根据当前路径高亮,Tags 页面把标签计数转成 TagCloud,具体标签页再过滤文章并复用 NoteCard。每个组件各自维护一份文章数据看似简单,时间一长就容易排序不一致;这里尽量让数据在 Hook 层集中,组件只管展示。

部署目标是静态托管,所以文章索引在构建时完成,线上不需要 Node 服务或数据库。build 命令先执行 TypeScript 构建和 build-notes,再由 Vite 生成 dist。发布命令把静态资源推到 Tencent Cloud Hosting,站点运行时只负责下载 HTML、JS、CSS 和文章数据。

视觉系统:不只是一张好看的背景图

视觉部分用 Tailwind CSS 自定义变量和动画实现。波浪背景、猫咪加载动画、文章卡片、时间线和响应式断点各有独立组件;明暗主题通过 Zustand 管理并持久化到浏览器,CSS 根据主题状态切换颜色与背景。

Header 里的 Neko 主题切换让“猫咪”不只是个 Logo,而是贯穿交互的一点小线索;mouse-firework 则在非链接区域响应鼠标移动,产生粒子效果。它们都属于增强阅读氛围的附加层,不能阻塞文章加载,也不能让键盘用户没法导航,所以视觉效果和内容结构保持相对独立。

站点还留了一个“宝藏”区域,里面放着电路、接缝等实验页面。这些页面和文章系统共用 Layout 与主题状态,但不参与装修文章索引。于是网站可以同时当知识库和前端实验场:一边认真记录验收细节,一边让猫在页面角落里继续捣乱。

静态部署下,缓存和路径要一起考虑

文章索引在构建时写进 public/notes.json,正文和图片也是静态资源,所以线上不需要数据库连接。但这也意味着内容更新依赖一次完整构建:只改了 Markdown 却没重新生成索引,列表页可能看不到新文章;只更新了索引却忘了传图片,详情页又会缺图。

React Query 的缓存适合文章变化不频繁的站点,但发布新内容后仍然要考虑浏览器缓存和静态资源缓存。文件名、文章目录和索引更新时间保持稳定约定,能减少“本地已经更新,线上还在看旧版本”的错觉。个人博客没有复杂发布平台,反而更需要把构建产物和部署边界写清楚。

路由用的是 React Router 的 createBrowserRouter 和 RouterProvider,文章、归档、标签与“宝藏”页面都有正常路径,比如 /note/:title、/archives 和 /tag/:tag。它走浏览器 History API,所以静态主机部署时还要保证未知路径能回退到入口 HTML;不然站内点击一切正常,直接刷新深层链接却可能吃一个 404。这个小细节和文章索引一样,属于“本地没问题、上线才发现”的部署边界。

Markdown 内容如何变成可读页面

构建索引时,脚本把 front matter 和正文拆开;React 页面拿到条目后,交给 Markdown 渲染层和统一的文章样式。标题、段落、列表、代码和图片有稳定的排版规则,文章内容不需要知道页面的布局细节。

元数据里同时保留创建时间和更新时间,是因为装修笔记经常会被补充:第一次记录水电验收,之后可能追加现场照片、材料规格或返工结果。归档排序看更新时间,读者仍然能看到文章是什么时候开始写的。对生活记录来说,这比只留一个模糊的发布时间更有信息量。

一些小功能背后的取舍

React Query 的缓存让切换首页、归档和标签页时不必重复请求同一份索引;懒加载减少初始 JavaScript;静态部署压低了运行成本;把图片和画面留在浏览器则省掉了为视觉功能搭后端的功夫。这些选择并不追求“技术栈越新越好”,而是围着个人站点的真实访问模式转:内容更新不频繁,阅读访问多,构建时可以多做一点工作。

与此同时,构建脚本要足够严格,主题状态要能恢复,动画要允许被关闭或绕开,路由在静态环境里要稳定。博客看起来是轻项目,实际上同时面对内容正确性、首屏体验、移动端布局和部署路径四种问题。把这些提前放进约定里,后面写文章才不会每次都重新踩一遍坑。

这个项目最像什么

XiaoHeiBaiMeow 有点像一间正在装修的数字房子:Markdown 是堆在房间里的材料,build-notes.ts 是负责清点材料的工人,React 是墙面与动线,Tailwind 是油漆和灯光,猫咪动画则是那只无论如何都要踩过刚拖完地面的猫。

它没有复杂后端,却有内容目录、元数据解析、构建索引、标签聚合、动态详情、主题持久化和静态部署这一整套小而完整的闭环。对我来说,个人博客做到“新增文章不需要改代码,读者能顺畅找到文章,部署过程不靠手工祈祷”,就已经是非常舒服的结果了。喵。ฅ(=ω=ฅ)

再往后做,我会继续把“内容正确性”和“视觉可爱”分开维护:前者由构建脚本、类型和路由保证,后者由主题、动画和组件负责。这样猫咪可以继续撒欢,文章也不会因为某个粒子效果加载失败就失去阅读能力。

项目地址:github.com/Acceyuriko/XiaoHeiBaiMeow,线上体验:jiangjiang.zone。

文章目录
  1. 1. 内容的源头:一个目录,一篇 Markdown
  2. 2. 前端只请求一份事实
  3. 3. 一篇新文章从写完到上线,会经过什么
  4. 4. 路由、懒加载和静态站点的现实
  5. 5. 视觉系统:不只是一张好看的背景图
  6. 6. 静态部署下,缓存和路径要一起考虑
  7. 7. Markdown 内容如何变成可读页面
  8. 8. 一些小功能背后的取舍
  9. 9. 这个项目最像什么
|