折腾 | 用数据文件优化短代码的使用
本文解决的是 Hugo 页面里结构化内容难维护的问题:把原本塞在 Markdown 里的 Shortcode 参数迁移到 Data Files 中,让内容数据、页面结构和展示代码分离。现在这个思路不只用在书影记录,也扩展到了书刊、影剧、摘录链接和足迹年度表格这些页面里。
适用场景
适合在 Hugo 中维护书单、影单、友链、项目列表、旅行足迹、年度清单等重复内容。只要内容条目的字段相对固定、数量会持续增加,就可以考虑用 Data Files 替代一条条手写 Shortcode。
如果你的页面只有三五条内容,直接写 Markdown 最简单;但如果一年下来会积累几十本书、几十部电影、很多条视频链接,甚至还要跨年份展示,那就很适合把「数据」从正文里拆出去。
背景
网站上原来有一个书影游页面,用来记录我看过的书和电影。为了显示效果,我一开始写了一个 mediacard Shortcode,每条记录都在 Markdown 里调用一次。
大概长这样:
| |
刚开始记录不多的时候,这种方式还挺直观。但后来问题越来越明显:
- Markdown 文件越来越长,一条记录就是一大段 Shortcode;
- 书和电影混在页面里,想按年份、月份整理很麻烦;
- 如果卡片样式要改,虽然可以改 Shortcode,但字段结构本身还是散在正文里;
- 后来又想记录文章、视频、播客摘录,继续堆 Shortcode 会越来越乱;
- 足迹页面也有类似问题,年度、月份、城市、活动这些信息本质上也是结构化数据。
所以这次折腾的核心不是「写一个更厉害的短代码」,而是反过来想:页面正文应该只描述页面,重复条目应该变成数据。
现在的数据文件结构
现在本站用到的数据文件主要放在 data/ 目录下,其中和这篇文章最相关的是四个:
| |
这里有一个和最初版本很不一样的变化:现在数据不再是一个扁平列表,而是优先按年份分组。
最早我只是把所有书塞进 books.yaml,再靠 type: 24-08 这种字段过滤年份。现在的结构更接近这样:
| |
这种结构有两个好处:
- 文件打开后,一眼就能定位到某一年;
- 渲染年度页面时,可以直接
index site.Data.books "2025",不用每次遍历全量数据再筛选。
当然,type 字段还是保留了,因为它承担的是「月份归档」的职责。也就是说,年份由外层 key 负责,月份由 type: YY-MM 负责。
四类数据分别管什么
1. books.yaml:书刊清单
books.yaml 用来放读过的书、网文、杂志等,核心字段是:
| |
这里我没有把字段设计得特别复杂,基本还是围绕一张卡片需要什么来定:封面、链接、作者、月份、评分和短评。这样数据文件虽然朴素,但和页面展示是一一对应的。
2. movies.yaml:影剧清单
movies.yaml 和 books.yaml 结构几乎一样,只是 author 在这里更像「导演 / 主创」。保持字段一致有一个现实好处:书刊和影剧可以共用同一个渲染 partial,不需要写两套卡片模板。
| |
字段统一之后,模板里只需要接收一个 items 列表,然后按卡片样式循环渲染即可。对我来说,这比为书和电影分别维护两个 Shortcode 更舒服。
3. mental_links.yaml:文章、视频、播客摘录
后来我发现,「书影游」其实不只应该包括书和电影。平时看到的一些文章、视频、播客,也很值得留下索引,所以又单独拆了一个 mental_links.yaml。
它的字段更轻一些:
| |
这里没有封面、评分和短评,因为链接摘录更像一个「稍后回看」列表。我只关心它是什么类型、叫什么、来自哪里、链接在哪。
4. footprint.yaml:足迹年度表格
footprint.yaml 则是另一个方向:它不是卡片流,而是年度表格。现在里面按分类组织:
| |
这里的关键是 travel / reading / poster 三个分类。它们共用相同的年度表格结构:每一年有一个 title,下面是十二个月,每个月可以有多个 events。
这样做之后,足迹首页、年度出游、年度书影游、海报墙这些页面就有了同一套数据底座。页面要怎么展示是一回事,但「这一年有哪些月份、每个月有哪些事件」这件事,不再散落在多个 Markdown 文件里。
页面结构怎么变了
数据拆出去以后,Markdown 页面就清爽多了。以 content/pages/footprint/2025/reading.md 为例,它现在主要只保留页面元信息和一小段说明:
| |
这里最重要的是两个 front matter 字段:
year = 2025:告诉模板当前页面要拿哪一年的数据;category = "reading":告诉模板当前页面属于足迹里的哪个分类。
也就是说,页面不再负责列出所有书和电影,只负责声明「我是 2025 年的书影游页面」。真正的数据读取和渲染,交给 layout 和 partial。
底层模板怎么串起来
1. 年度足迹页面读取 data/footprint.yaml
layouts/pages/footprint-year.html 会先从 front matter 里拿到年份和分类:
| |
这里其实就是两次 index:先通过 travel / reading / poster 找到分类,再通过 2025 / 2024 / 2023 找到年份。
如果是出游或海报墙页面,模板会把 $data.rows 渲染成年度表格;如果是 reading 页面,则会走另一条分支:
| |
这段逻辑的意思是:reading.md 自己的 Markdown 正文照常渲染,然后再把这一年的书、影、摘录都交给 mentalfood/year.html 这个 partial 来补上。
2. mentalfood/year.html 聚合三份数据
layouts/partials/mentalfood/year.html 只关心一件事:给定一个年份,把这一年的书刊、影剧和摘录分别取出来。
| |
拿到数据后,它会渲染三个折叠块:
- 书刊:
partial "mentalfood/media-list.html"; - 影剧:同样复用
partial "mentalfood/media-list.html"; - 摘录:
partial "mentalfood/link-list.html"。
我比较喜欢这个拆法:年度页面只负责「这是哪一年」,year.html 负责「这一年有哪些数据」,具体列表长什么样再交给更小的 partial。每一层都只做一件事,后面要改样式或新增字段时比较不容易牵一发动全身。
3. media-list.html 统一渲染书和影
书和影之所以能共用 media-list.html,是因为它们的字段结构保持一致。渲染时大概就是循环 items:
| |
这样一来,新增一本书或一部电影时,我只需要补 YAML;卡片怎么排版、封面怎么显示、评分怎么着色、没有封面时怎么兜底,都是模板负责。
4. medialist 仍然保留,但已经不是主入口
现在年度书影游页面主要走 footprint-year.html + mentalfood/year.html 这套新结构。不过原来的 medialist Shortcode 还保留在主题里:
| |
它最初的使用方式是:
| |
不过要注意:medialist 是早期「扁平数据 + 年份前缀过滤」阶段留下的过渡层,而现在主数据已经改成了外层按年份分组。因此新页面不再依赖它,主要使用 layout + partial 直接读取 site.Data.books "2025" 这种结构。这样 Markdown 里就不用再写展示逻辑,页面也更接近「声明年份和分类」的角色。
为什么要按年份分组
最开始用 Data Files 时,我只想着「不要把数据写在 Markdown 里」。当时把 books.yaml、movies.yaml 做成一个大列表,再用 type 前缀筛选年份,已经比原来好很多。
但随着数据越来越多,这个结构又开始显得有点笨:
- CMS 表单里不好按年份折叠;
- 模板每次都要遍历全量数据;
- 想检查某一年有没有漏记,需要在长列表里搜索;
- 后续足迹页面本来就是年度维度,书影数据也应该跟它对齐。
所以现在改成了外层年份 key:"2025" -> 列表。这样既方便 Hugo 模板 index,也方便 Pages CMS 把每一年做成独立字段。
Pages CMS 也接进来了
这次数据结构调整还有一个现实原因:我现在已经把博客的很多编辑入口搬到了浏览器里。.pages.yml 里把这几份数据文件都配置成了可编辑表单:
| |
并且每类数据都有对应的组件定义。例如书刊和影剧都要求 type 使用 YY-MM 格式:
| |
这一步挺关键的。只靠 YAML 文件当然也能维护,但字段一多之后,人总会写错。把它接进 Pages CMS 后,常用字段变成表单,type 这类容易写错的字段加上格式校验,日常更新就顺手很多。
也就是说,Data Files 解决的是「数据不要混在正文里」;Pages CMS 进一步解决的是「编辑数据时不要每次都手写 YAML」。
最终效果
现在新增一条书影记录,大致流程是:
- 打开 Pages CMS 的 Data 数据入口;
- 选择
books.yaml或movies.yaml; - 找到对应年份,比如
2025; - 新增一条记录,填标题、封面、链接、作者、月份、评分和简评;
- 保存后由 Hugo 构建,年度书影游页面自动渲染出来。
新增一条出游足迹也类似:只需要改 data/footprint.yaml 里对应分类、年份、月份下的 events,页面表格会自动更新。
对我来说,最明显的变化是:Markdown 页面终于不用承担所有事情了。它只保留页面说明和少量结构,真正会持续增长的内容都进入了数据文件。后续无论是按年份展示、换卡片样式,还是把同一份数据复用到别的页面,都会轻松很多。
小结
这次折腾从一个很小的问题开始:书影记录页面里 Shortcode 太多,维护起来很烦。最初的解决方案是把书和电影拆到 data/books.yaml、data/movies.yaml,再用 medialist 按年份筛选。后来随着网站结构继续演进,这套思路扩展成了现在的四份核心数据文件:
data/books.yaml:书刊;data/movies.yaml:影剧;data/mental_links.yaml:文章、视频、播客等摘录;data/footprint.yaml:出游、书影游、海报墙的年度表格。
最终我更想保留的是这个边界:数据放在 Data Files,页面声明年份和分类,layout / partial 负责组织与渲染,Pages CMS 负责降低编辑门槛。
这种方案不一定适合所有博客,但对于个人站点里那些「会不断增加、字段又比较固定」的内容来说,确实舒服很多。它没有让 Hugo 变复杂,反而让 Hugo 静态站点最擅长的事情更清楚了:用朴素的数据文件,生成稳定、可迁移、容易维护的页面。
参考资料
(2025-08-28@深圳)