我从 2018 年开始用 org-static-blog 写博客到现在也有几年时间了,写的文章比较少,总体上这个简单的静态网站生成方案都能够满足需求。
但中途也发现一些不够用的地方,比如之前想要并贡献了一个生成 meta description 的 PR ,以便更好地在搜索引擎展示结果( SEO );又比如中途一度有区分中英双语博客的需求( index 列表、 RSS 等),但苦于没有想到很好的方法,就只能把两种文章都混杂在一起展示,但这样用户的阅读体验不好;另外之前的样式也差强人意,比如主页的链接带下划线,不利于阅读。


最近又有了一些其他的想法,就考虑做一个升级方案,它需要能够满足以下需求:
- 支持 org-mode 标记语法
- 支持中英双语
- 较完善的 SEO 支持
- 较完善的社交分享功能
- 能集中展示个人的其他社交网站链接
- 除了博客之外,还能够展示其他的内容
- 界面稍微美观一点
进一步细化需求,需要考虑的因素有:
- [X] 保持 blog, tags, rss 链接不变
最重要的是已有的博文 url 不能变。但在这个过程中我发现这件事还没那么简单,配置起来很麻烦;最终决定 url 变了就变了吧,把原来的博客 html 内容改为 meta refresh 来重定向到新博文 url ,这样比较简单通用。
- [X] SEO 功能验证
- [X] robots.txt
- [X] sitemap
- [X] 支持 description ( front matter 已内置支持)
- [X] front matter 支持文章更新时间
lastmod 字段
- [X] 社交分享效果验证
- [X] 选择一个合适的主题 theme
需要支持的特性( 综合考虑,最终选择 beautifulhugo ):
- [X] 需要 responsive
- [X] 多语言支持
- [X] 展示 social links
- [X] 社交分享支持
- [X] 首页文章倒序,最新的在最上面
- [X] 文章内部如何支持显示更新时间、其他语言版本?
不支持
- [X] 支持 toc 目录
暂不支持,但可以接受: https://github.com/halogenica/beautifulhugo/issues/219
- [X] 验证分页条数及效果
默认 10 条
根据 org-mode 官网 Blogs and Wikis with Org - orgmode.org 推荐,大致考察了几种方案:
- Jekyll 不支持 org-mode ,仅支持 Markdown ;采用 Ruby 编写的,不太熟悉;
- Hexo 不支持 org-mode ;采用 Node.js 编写,庞大的依赖让人望而却步;
- Hugo 支持 org-mode ,采用 Golang 编写,只有一个二进制文件,无依赖;另外它编译 org-mode 文件到 HTML 的速度极快(后面会提到);
最终决定采用 Hugo ,我本以为很快能搞定,结果没想到正确配置 Hugo 并没有那么简单。
迁移过程及遇到的问题
第一次使用没有经验,需要先建一个 hello world 工程来玩玩。
按照官方文档 Quick Start | Hugo - gohugo.io ,先 hugo new site hello-world
创建一个项目,然后修改主题 theme = "ananke"
,并新建一个 post 然后 hugo server -D
启动开发版 web server 。
到目前为止,整个过程都很顺滑,尤其是它的编译速度非常快,只要 100+ms ,这着实惊艳到我了!🤯而且 Hugo 支持实时加载(live reload),就是当你更新并保存了你的 org-mode 文件之后, Hugo 会自动重新编译并触发浏览器刷新当前页面,减少了人工操作,写作体验超级丝滑!

用 Git 保存好当前的 config.toml
,继续尝试。
然后按照文档指引,根据多语言的设置配置,结果发现 ananke 并不能很好地支持多语言(如果没记错的话,它不支持目录结构的多语言模式),于是不得不换别的主题尝试。
结果发现换一个主题之后,原来 ananke 主题下的 config.toml
在新主题下却不能工作了。
回顾整个过程,遇到的主要问题主要有2个:
- 多语言目录结构是怎样的?
- 如何调整一个主题的设置?
一开始以为 Hugo 主题只负责 UI 的渲染,与 Hugo 核心没有太多耦合的东西,一个 config.toml
就能够轻松适配多种不同的主题。结果在更换主题的过程中发现并不是这样的,而且每一种主题一般都有一个样例配置 exampleSite/config.toml
,这才渐渐明白 Hugo 的配置其实是 以主题配置为中心 ,从主题附带的示例配置出发进行微调,整个过程会顺利很多。
多语言的目录结构
接下来说说多语言的目录结构问题。
关于多语言的设置,文档有一页 Multilingual Mode | Hugo - gohugo.io 专门细说。主要有两种方式:
- 同一个文件夹下,以不同的文件名“后缀”表示比如的语言,比如
hello-hugo.en.md
hello-hugo.zh-cn.md
分别表示同一篇文章的英文和中文版本。 -
通过不同文件夹来区分,比如一个
en
文件夹专门放英文文章,另外一个zh-cn
专门放中文文章。 官方文档给出的示例是这样的:/content/english/about.md /content/french/about.md
我比较倾向于第二种方式,这样文件归类比较清晰明了。
但是,事与愿违,偏偏在选主题的过程中发现很多主题都只能支持第一种方式,不支持第二种。
而且要命的是,对于第二种方式的目录结构,文档也没有明确的说明,导致我一直没有弄明白正确的目录结构。
比如,我这样设置中英文博文的内容目录 contentDir :
[languages]
[languages.en]
contentDir = "content/en"
languageName = "English"
weight = 1
[languages.zh-cn]
contentDir = "content/zh-cn"
languageName = "简体中文"
weight = 2
最初我以为配置要求的目录结构是这样的:
content/
posts/
en/
zh/
但通过摸索实践,最终发现它其实要求这样的:
content/
en/
posts/
zh/
posts/
这里发现的过程还挺有意思的,算是一种探索、发现新知识的手段。在经历了各种搜索都找不到结果、官方文档也很模糊的情况下,如何得知它的正确目录结构这个知识呢?——“大胆尝试,小心验证。”
要不试试新建一篇文章,看它放在哪里了?
$ hugo new posts/test.org
/path/to/hello-world/content/posts/en/posts/test.org created
通过琢磨这里新建文件的路径,我终于明白了多语言的目录结构。如上边所说,其实就是每种语言目录都是一个新的根目录,当时还特意发了一条推文宣布这个发现:
@GoHugoIO 's config of multilingual mode is tricky, the correct directory hierarchy is like picture #2, instead of picture #3.
— whatacold (@whatacold) July 6, 2021
Don't ask me how I figured it out, at last...😩😩 pic.twitter.com/9evBfsvH7s
TOML 文件格式
TOML 现在的全称叫 Tom's Obvious, Minimal Language (这个 Minimal 挺难评的),以前叫 Tom's Own Markup Language 。由 Tom Preston-Werner 创建,他是 GitHub 的联合创始人,也是个开发者。
TOML 语法及 Hugo 配置语法实在不怎么好用,故这里对我比较困惑的地方做个简单的了解和记录。
- TOML 语法本身区分大小写,但是 Hugo 配置似乎不怎么区分。比如我测试发现
Params
vsparams
都可以。 - 开头的缩进不起没有作用,所以才会有
[languages.en]
这样的写法用于表示配置结构上的缩进。 - Array of Tables 用 Clojure 的话来讲就是一个 map 数组。
详细的语法规格见 TOML spec 。
最终效果
经过对原来 org-static-blog 的 org-mode 源文件进行了一些微整,以及对 beautifulhugo 主题的修饰之后,终于生成了现在这个比较满意的效果。
对于 org-mode 源文件的调整,有以下微调:
- date tags 这两个 front matter 格式调整
- 图片路径更改
-
博文的 URL 调整
由于博客访问量并不大,我可以接受 URL 变更,但原 url 还是保留,毕竟 404 对于读者太不友好了。具体做法就是,原博文的 html 通过 meta tag 来实现类似的 301 重定向,比如这样:
<!DOCTYPE html> <html> <head> <meta http-equiv="refresh" content="0; url=/zh-cn/blog/2021-02-27-wrk-yuanli/"> </head> <body> <p>The page has moved to: <a href="/zh-cn/blog/2021-02-27-wrk-yuanli/">here</a></p> </body> </html>
对于 beautifulhugo 主题,主要参考 Keep Coding - liujiacai.net 的网站进行了配色调整,更加便于阅读。(具体做法是,拷贝一份 beatifulhugo 仓库中的 main.css 到自己工程 static/css/main.css
,然后再进行修改微调。)
遗留问题
虽然现在网站结果总体比较满意,但还存在一些小问题:
- 每一级标题无法使用编号,不利于阅读
-
beautifulhugo 页面性能较差,打开一片文章会发起 30+ 个 HTTP 请求
Lighthouse 测评结果表明性能较慢 -
本地预览版本也会启用 Google Analytics ,污染数据有两种解决方法:
- 维护两份配置,本地预览和部署版本通过
--config <xxx.toml>
来区分,也可以同时传入多个配置文件,通过,
逗号隔开 -
以目录组织配置文件,通过命令行参数
--production
来指定环境;环境名就是 config/ 内的子目录名,_default/
目录放兜底的全量配置。比如这样的目录结构:
$ tree config/ config/ ├── _default │ └── config.toml # 全量配置,不配置 GA id └── production └── config.toml # 配置 GA id 2 directories, 2 files
第二种方法比较符合我的胃口,配置结构清晰。有了上面的配置结构,通过
hugo server -D
启动的本地版本就不再上报 GA ;修改博客达到满意的效果之后,再hugo --environment production
生成包含 GA 代码的博客全部 html 文件。参考:
- 维护两份配置,本地预览和部署版本通过
footer 中的版权年份展示不对,按道理应该要展示到 2021 年(突然又好了,不知道为何) 文档说明在 https://gohugo.io/variables/site/
Hugo 使用感受
Hugo 优点:很快,基本能满足我的大部分需求。
不足之处:
-
文档看起来很全,但是却很难快速地找到有效信息; 比如:
- 多语言的目录结构
- 配置文件参数有点乱,比如
[params]
vs.[Params]
齐飞(第一个字母大小写无关?),比如[Params]
参数存在的意义(或者说为何要定义这么一节配置)?
- 配置
config.toml
与主题强相关, 换一个主题基本要重新配置 ,这点在文档中也没提到; 根据这几天的经验,我的最佳实践是,从选定主题的仓库中 copy 一个初始的 exampleSite 配置,然后再慢慢微调,这样会比较省力😼。 -
有的 org-mode 特性 Hugo 不支持,比如:
include
指令- 不支持 Evaluating Code Blocks
总体而言,如果你也是 Emacs 和 org-mode 用户,那么 Hugo 来写博客还是不错的,可以入坑👍。