2026年我好了伤疤忘了痛再次使用static site generator建立个人博客的下场
how this all began? 怎么又来?
这一切的初衷是我想:
- 在博文里正常(以现代软件之姿)贴图(否则在时间物质——一个writefreely平台——已经完全够用了)。
- 有一个存储迄今为止比较有用文章的地方,最好是用markdown格式,而且发布到任何地方都能直接从这里复制——其实就是维护 a single source of truth。
从十年前也就是2016年开始,我就开始反复制作自己的网站,并在这个过程中终于入门了前端三件套。就在我渐入佳境时,llm闪亮登场,于是我又失去了大部分前端能力,变成了躺在llm怀里嗷嗷吃奶的小婴儿。此为题外话。
即使技术力下降了,经验却不会丢失太多。我很快锁定了一些建站方案,并颇为自信地认为有了llm和个人知识的加持,我将水来土掩轻松上手任何一个static site generator。但接下来的经历却表明,人类(至少是我)往往会忘记过去的创伤,又在第n次吃一堑长一智中痛苦地恢复记忆。
最初的方案们
- 手搓充满前卫艺术感的千禧年barebones网页:不了。这次我只想正经写一些文字。因为我发现,过去写下的文章还是很有用的,特别是我给自己编写的emacs文档(此处应有链接)曾多次拯救我于水火。
虽然这么说,将来我可能会抵挡不住诱惑,再建立一个符合自己写作需求的网站,例如我一直很想以文字游戏剧本的形式建立网站。 - zim wiki:一个经典的方案,问题是有点过于经典,近乎山顶洞人。zim wiki(此处应有链接)是一个老实巴交的wiki程序,生成的文件结构是一个嵌套文件夹,里面是一堆txt。如果往zim wiki里贴图,它会把图片复制到当前页面的同级目录,之后用户可以直接生成html站点。它引用资源的方式如此朴实而牢靠,令人感动。但是我不想手动生成html部署。这不现代。
- hugo
- 在llm帮助下自己写一个ssg,也是时候这么做了:也许将来我会的。
- 用typora写文章:经典之选。
- 用自己的奇怪数据库格式存数据:也许我会用一全新的格式存储我的个人journal数据,但现在还是算了吧。
- 用一个别人写的小众怪异ssg,或者干脆是wiki程序:有一些备选,但终究没有动手。其中包括那么的维基。这个推特形式的博客或wiki程序应该很好用,但我这个博客的定位将会是超长篇大论。我已经有很多碎片笔记了。I don't need no more.
- 基于pandoc的ssg……那和自己手搓ssg并没有区别啊!
- 摆烂,直接写在github issue里:有点太绝望了。
- writefreely+github图床:TODO。
hugo,不祥的开局
最终还是先尝试了hugo,所谓“先尝试”,就是打开hugo theme gallery,看看有没有自己喜欢的主题。
我喜欢的主题
- not-much
- 缺点:移动端菜单非常难找,和背景颜色几乎一样;窄屏下toc有点小bug(可以维修):

但是,总觉得typography太挤了,没那么优雅——例如各级标题只用#的个数而不是字体大小作区分,感到不适。
- 缺点:移动端菜单非常难找,和背景颜色几乎一样;窄屏下toc有点小bug(可以维修):
- dario
- 缺点:它模仿的Dario Amodei有目录,它却没有。目录(文章侧边栏那个toc)对我来说非常重要。再重申一遍我要写长文;typography看起来没有那么舒适,有种要改很多的预感。
- Shibui (渋い)
- 优点:真的很美丽;移动端有适配。
- 缺点:没有侧边toc,toc只能放在文章开头;它致敬的William Jansson网站文章列表样式更好。
- 其实这个主题现在看看还是不错的。
- Tufte
- 我只是对tufte css有执念。虽然我并不知道文章哪里需要用到side note。side note在markdown编辑器里一定也很难看吧。
- 缺点:也没有toc。
- ritzy | Dominik Ritzenhoff
- 看起来优雅简单,但恐怕有点太简单。没有toc也没有太多格式演示。
炼狱之旅(只是炼狱,不是地狱)
最后我选择了tufte。选择的过程涉及一些玄学,回头想想,这么简单的事情为什么要用玄学呢?但或许正因为无足轻重,所以可以用宇宙的随机状态来决定吧。
我开始归档以前的github pages repo,并创建hugo站点。这个过程很简单。先用winget安装hugo本体,然后把tufte以submodule的形式链接到这个repo。echo "theme = 'tufte'" >> hugo.toml出了点小问题,不知为何echo写入的全是乱码,但手动打开写入正确内容后解决了。
当我键入hugo server的时候,一件悲伤的事情发生:
(此处省略很多行)...execute of template failed: template: _partials/header.html:15:3: executing "_partials/header.html" at <partial "header.includes.html" .>: error calling partial: maximum template call stack size exceeded in "<path to my blog>\\themes\\tufte\\layouts\\partials\\header.html"
好吧,循环引用。搜索了一下github issues发现要改成:
After replacing
resources.ToCSSwithcss.Sassinheader.includes.html, I was able to build with hugo v0.144.1, but still got the same error with the latest version!
这么修改之后就有新的错误了,真是好消息不断啊:
executing "_partials/footer.html" at <.PrevPage>:
can't evaluate field PrevPage in type *hugolib.pageState
这个还比较容易修,只需要把所有 .PrevPage改成.Prev,把所有 .NextPage改成.Next就行了。
然后hugo server,一切顺利。
炼狱中的沉思:前端大爆炸及其他
这时我看着空空荡荡的localhost:1313,陷入了沉思,整个大脑浸入热水浴般的疲惫。倒不是因为修那几个bug多么费事——只有两个而已——而是这一切都让我仿佛回到了2018年,那时候只要有借口不做作业,我可以彻夜改写网站的模板文件,而且别忘了那是个没有llm的时代,陪伴我的只有一次次网页崩溃和搜索。
而且我想起,在那些前端大冒险中,我几乎没学到什么有用的前端知识,除了跟着时灵时不灵的网页教程操作、咬牙解决一期一会的bug、培养出卓绝的忍耐力之外。
因为那些代码并不是我写的。我得解决一大堆我并不理解、超出我知识范畴的问题。最后我并没有留下什么有用的记忆。
只有几年后,我开始用vanilla js和css构建非常简单的网页应用之时,才悟到前端的现状是层出不穷的叠床架屋。无数工具为“省事”而发明出来,但作为入门者,如果我从来没感受过“费事”,我永远不会知道我用那些package都省了什么事。
我也承认我并非一个那么爱尝试新事物、爱折腾的hacker。但也不止我一个人觉得前端世界一团乱麻。越深入这个领域,我越觉得这里有一种浮躁的性格在起作用。I feel people tend to show off a lot in the world of frontend, but that tendency actually helps the career, because frontend is about veneering.
回归正题。2018年的时候我还年轻,我还不是那么opinionated,还在跃跃欲试,并觉得世界上一切存在都有其道理。那时候我不会觉得.PrevPage到.Prev的deprecation有什么问题,不假思索改了就行。但现在我只觉得这种修改背后可能隐藏着一百个糟糕的设计。
我疑心病犯了。
当然,我应该对它们更宽容一点。ssg并不是一个简单的系统,因为用户可能想用它建立各种网站——文档,博客,landing page,简历,等等。
我逐渐回想起ssg要包含的各种功能:嵌套模板,i18n,config(噢天哪,config!),pagination,category & tags,统计数据,评论区,插件……一大堆东西。我也同时头皮发麻地回想起我和这些功能打交道的痛苦过程。
总之,ssg得在通用性和易用性之间做出权衡。如果设计得过于通用,那编写config就会是一场苦旅。config和内容frontmatter会越来越复杂,直到模板比生成出的页面还长——哦,对了,最通用的ssg系统不就是手搓每一个html文件吗?
ssg的主题系统也已经不只是主题了,而是一套完整带样式的内容模板——如果你下载了一个用于简历的主题,那就很难把它改成博客。至少hugo是这样的。每个主题的内容模块和样式都打包在一起,每个可能都implement了自己的评论系统配置方式,一旦切换,就要重写配置……
我想起,正是因为受不了这些“多余之物”,才会在2021年左右回到vanilla网页——宁愿手写html,也不想再吃不受自己控制的代码之苦了。
hugo + obsidian?
一开始我是想用obsidian来配合hugo的,但最后没有实行。
Some links I've found but haven't read on this topic:
- How to Optimize Image Insertion and Management with HUGO and Obsidian? - support - HUGO
- My Obsidian + Hugo blogging setup | 4rkal's Dev Blog
- Publishing an Obsidian vault with Hugo - Jacob Kaplan-Moss
- Obsidian is the perfect Hugo CMS – Nick Gracilla
astro 和 retypeset theme,一段孽缘
obsidian-digital-garden,返璞归真
Digital Garden - Publish Obsidian Notes For Free
- 把
titleproperty设置为我需要的标题 - 打开inline title
custom css
为什么默认首页title是notes?
修改路径导致文章重复
这时需要重新发布所有文章,而不是publish active note,才能删除错误的重复文章。
特别顽固的白色背景
最后以这样的方式修好了:
.theme-light {
--background-primary: #f8f7f6;
--background-secondary: #f3f2f1;
--text-normal: #333;
background: var(--background-primary);
color: var(--text-normal);
}
260223-markdown-test is weirdly broken from time to time
analytics
根据这个issue操作,只需要:
- 打开vercel analytics(因为我把网站host在vercel上,它这个免费套餐可以看30天的数据)
- 在我的网站repo里新建
src/site/_includes/components/user/common/footer/analytics.njk(github可以直接新建文件,键入/会创建新目录) - 在里面粘贴:
<script defer src="/_vercel/insights/script.js"></script> <script defer src="/_vercel/speed-insights/script.js"></script>
这样就完成了。
dataview列表的标题里不能包含|
否则好像|后面的内容都会被切除。