#前言
Hugo 是一个基于 Go 语言开发的静态网站生成器(SSG),目前由 @bep 领衔开发,Hugo 的突出特点是简洁、灵活、高效,目前有很多知名网站都在使用 Hugo:Netlify、Let’s Encrypt、IPFS、Cloudflare Developers、DigitalOcean Docs、1Password 等等。与目前国内流行的 Hexo 相比,Hugo 的速度可称为飞速🚀——在安装和使用上都是如此。
下面,我们就来看一下 Hugo 与 Hexo 的一些异同,不过需要注意的是:此文主要探讨的是用户层面上的一些异同,而非技术层面的差异。简体中文来说🐼:即一个技术小白(我)从 Hexo 迁移到 Hugo 时遇到的一些常见问题。
#目录结构
~/blog $ tree -L 1
. # 说明 Hexo
├── archetypes/ # 文章模板 scaffolds/
├── assets/ # Hugo 管道
├── config.toml # 配置文件 _config.yml
├── content/ # 文章目录 source/_posts/
├── data/ # Hugo 数据文件 source/_data/
├── layouts/ # 布局模板
├── public/ # 生成的静态文件 public/
├── resources/ # Hugo 缓存
├── static/ # 网站的静态文件 source/
└── themes/ # 主题目录 themes/
在 Hugo 中,与 Hexo 的一个很大不同——也可以说是优点——是主题目录与站点目录有一样的结构,以 MemE 主题举个例子(已剔除无关文件):
~/blog/themes/meme $ tree -L 1
.
├── assets/
├── data/
├── i18n/
├── layouts/
└── static/
其中,assets
、data
、layouts
、static
的作用都是与站点目录下的相应文件夹相同的,且站点目录下的文件可以覆盖主题目录下的相应文件——这意味着你可以在不修改主题文件的前提下方便地定制主题。在 Hugo 中,如果你想要定制主题,你只需在站点目录下新建相应的文件即可。这是非常利于主题的维护的,你只需使用 Git 的 submodule
的方式安装 Hugo 的主题,然后更新时只需直接在站点根目录下敲一条命令回车即可,非常方便!同时,这在持续集成部署时也会方便很多,这点你可以在 GitHub 中看出来,如下图。
此外,上面的 i18n
文件夹相当于 Hexo 的主题中的 languages
文件夹,如果你不喜欢主题的一些文字翻译,可以在站点目录下新建相应文件自定义。这里特别需要提醒的是,Hugo 中的 data
和 i18n
文件夹下的所有文件都是可以按「键」覆盖的,即你无需复制文件全文,只需添加你想自定义的那项即可。
#配置文件
Hugo 中是不区分站点和主题的配置文件的,Hugo 中只有一个位于站点根目录下的 config.toml
配置文件[1]。你可能注意到 .toml
后缀,没错,Hugo 默认使用的配置文件是 TOML 格式的,它的语法是非常简单易懂的,它在语法上也没有缩进的要求[2]。当然,在 Hugo 中你也可使用 Hexo 默认的 YAML 格式,但我还是建议你使用 TOML,毕竟入乡随俗嘛😺。此外,如果你想将文章中的 Front Matter 也从 YAML 转换成 TOML 的话,推荐一个 Python 脚本,是我在这篇文章中发现的。但特别注意:尝试前务必先备份!
#分类方式和组织方式
实话实说,这应该是从 Hexo 迁移到 Hugo 中遇到的最难的问题了,主要是概念上很难转过来。我们知道,在 Hexo 中有两种分类方式——分类和标签,它们都是在文章的 Front Matter 中设置的,其中:categories
是具有顺序性和层次性的,即你可以通过它来实现树状结构的分类;tags
则没有这种特点,它的作用只是将不同的文章联系起来。由于 Hexo 中的 categories
具有这样的特点,因此在 Hexo 中 categories
可以作为一种文章的组织方式——在 Hexo 中你可以将 categories
用在文章的 URL 结构中。
但是在 Hugo 中这是无法实现的,在 Hugo 中你是无法将文章的 Front Matter 中的 categories
用于文章的 URL 的。为什么呢?因为 Hugo 中的 categories
与 tags
在功能上其实是完全相同的,它们的作用都是将不同的文章联系起来。其实,Front Matter 中的 categories
和 tags
在 Hugo 中都属于 Taxonomies:
Hugo includes support for user-defined taxonomies to help you demonstrate logical relationships between content for the end users of your website.
简体中文来说🐼:「Hugo 支持用户定义的类别(taxonomies)来帮你为自己网站的读者展示文章之间的逻辑关系」。也就是说,在 Hugo 中其实你可以自定义自己想要的任何类别,而不仅限于部类(categories
)[4]和标签(tags
),比如:你可以自定义一个 series
,也就是系列╱专题╱专栏。
既然 Hugo 中基于 Front Matter 的部类和标签都属于类别,而类别的作用只是联系文章,部类自然不可能有树状结构,自然也不能用部类来组织文章。那么,在 Hugo 中你要怎么组织文章呢?分区(Sections)。所谓分区,即站点的 content
目录下的文件夹和子文件夹,一个文件夹即一个分区[5]。很明显,这是基于文件系统的结构的,自然也就支持树状╱网状╱嵌套结构,也就能够用来实现文章的树状分类。我觉得这是 Hugo 的又一个优点,直接利用起文件系统的结构来组织文章,合理且符合用户预期,Markdown 文档的存放也更有序。
#URL
迁移博客过程中最重要的莫过于保证文章的 URL 不变,不然,这将会非常不利于 SEO。好不容易有一篇文章出现在 Google 相关搜索结果的前排,却因为 URL 的变化导致原链接 404 从而导致该文章从 Google 的索引中移除,这一定会是非常令人沮丧的😶。
当你从 Hexo 迁移到 Hugo 时,如果你在 Hexo 中的站点配置文件 _config.yml
的 permalink
中只使用了 :year
、:month
、:day
、:title
变量的话,那么文章的 URL 是能够平滑地迁移到 Hugo 的,见 URL Management。同时,提醒一点,Hexo 中的文章中的 Front Matter 中的 permalink
在 Hugo 中对应的是 slug
。
但是,对于某些用户(比如我)在迁移时则可能会有一些麻烦,比如在 Hexo 中使用了 :category
、:id
、:abbrlink
变量,以及 .html
后缀。下面就来讲讲这些情况的解决之道。
首先就是部类,即 Categories 页面,也即 Front Matter 中的 categories
,也即配置文件里的 :category
。
正如上文所说的,Hugo 中的部类是与标签无差别的,所以在 Hugo 中你是无法实现多级的部类的,即当你从 Hexo 迁移到 Hugo 时,/categories/foo/bar/
这样的链接是一定会 404 的,除非你使用 Aliases。当然,这其实也无所谓,因为重要的是文章的链接,其它页面的链接可保持也可不保持。
对于文章的链接,如果你设计了复杂的多级部类(树状分类)且将之加入了文章的 URL 结构,那么在 Hugo 中保持文章 URL 中的这种结构的合理方式就是使用分区 Sections,然后在 Hugo 的配置文件 config.toml
中的 permalinks
配置相应的分区的 :sections
变量[6]。
然后是 .html
后缀,博主当时年轻,想着加上这个显得 COOL 一点,于是添加了此参数并随着那篇教程带偏了很多人。现在接触了 Hugo,博主知道了这样的 URL 其实是很丑的,所以也就不在 Hugo 中保持了。毕竟,现代的服务器都有类似的自动跳转,这意味着原链接并不会 404,故在 Hugo 中也没必要再去丑化 URL。
当然,如果你还是想在 Hugo 中保持文章的 URL 的 .html
后缀,也不是没有办法,利用 Hugo 配置中的 uglyURLs
和 Output Formats 的一个配置项 noUgly
即可实现。首先在配置文件 config.toml
的顶部加上 uglyURLs = true
,然后继续添加以下代码:
[outputFormats.NoUglyHTML]
mediaType = "text/html"
noUgly = "true"
[outputFormats.NoUglyRSS]
mediaType = "application/rss+xml"
noUgly = "true"
[outputs]
page = ["HTML"]
home = ["HTML", "RSS"]
section = ["NoUglyHTML", "NoUglyRSS"]
taxonomyTerm = ["NoUglyHTML", "NoUglyRSS"]
taxonomy = ["NoUglyHTML", "NoUglyRSS"]
这样就能实现仅文章的 URL 是有 .html
后缀的,但需要注意的是:这样是会产生一些 bugs 的——具体我就不再花时间叙述了——因此还是建议直接抛弃 .html
后缀。
迁移时另外一个比较重要的 URL 问题应该是中文部类和中文标签的英文别名问题,比如你添加了一个「树莓派」标签,但你不希望该标签的 URL 是 /tags/树莓派/
,你希望它的 URL 是英文的 /tags/raspberry-pi/
。在 Hexo 中你可以通过站点配置文件 _config.yml
中的 tag_map
来实现,但在 Hugo 中没有这样的功能,那在 Hugo 中应该怎么办呢?
首先,你需要在配置文件 config.toml
中配置下部类和标签的 URL 结构:
[permalinks]
categories = "/categories/:slug/"
tags = "/tags/:slug/"
然后,在你的站点根目录下的 content
文件夹内新建 tags
文件夹,并在它的里面新建一个 树莓派
文件夹,最后在该文件夹内新建一个 _index.md
文件,并加入:
+++
slug = "raspberry-pi"
+++
这样就解决了 URL 内的中文标签的别名问题。但还有一种特殊情况,就是你添加的标签如果比较复杂[7],比如 (最新版本 Hugo 中直接使用标签名作为文件夹名即可,即使具有特殊符号)Raspberry Pi(树莓派)
。那么此时,你在 tags
文件夹内新建的文件夹的名字就不能是标签的名字本身,而必须是 Hugo「链接化」后的标签名,比如 raspberry-pi树莓派
。然后,在上面添加了 slug
的基础上,再手动添加一行 title = "Raspberry Pi(树莓派)"
以修正标题。此外,如果你在配置文件 config.toml
中设置了 disablePathToLower
,那么新建的文件夹的名字就应该是 Raspberry-Pi树莓派
。
这里再来说一说 URL 中的英文字符的大小写的问题。在 Hexo 中如果你添加一个 Raspberry Pi
标签,那么它的 URL 是 Raspberry-Pi
,但在 Hugo 中它的 URL 会变成 raspberry-pi
,即将大写字母处理成了使 URL 更美观的小写字母。这也意味着当你从 Hexo 迁移到 Hugo 时,你的一些包含大写字母的原链接会 404,如果你想避免这个情况,你可以在配置文件 config.toml
的顶部加上 disablePathToLower = true
。但我还是不建议这样做,因为全小写的 URL 的确会更美观,即使这会导致原链接 404,但重要的是文章的链接,而非某一标签页面。
最后,对于以上方法,我只能保证在 MemE 主题中是正常工作的。此外,MemE 主题支持复杂的中文标签名,支持复杂的中文部类名,支持复杂的中文分区名。
对于分区,我推荐直接使用小写英文命名,然后通过新建相应的 _index.md
并添加 title
以修正标题。当然,如果你要用中文也可以,但建议用简单的中文名。然后,如果你想要使分区的 URL 却为英文,比如:你想使「系列」分区的 URL 为 /series/
而非 /系列/
,那么你需要先在「系列」文件夹下新建一个 _index.md
文件,然后加上以下内容:
+++
url = "series"
+++
最后在配置文件 config.toml
中配置下该分区下的文章的 URL 结构,比如:
[permalinks]
"系列" = "/series/:slug/"
对于从 Hexo 迁移到 Hugo 在 URL 上的其它情况,比如在 Hexo 中使用了 :id
和 :abbrlink
变量,那么你可以通过 url
在文章的 Front Matter 中直接手动指定文章的 URL,以保持文章链接的不变。
当然,如果在迁移时你想重构博客文章的 URL 结构,那么可以利用 Hugo 的 Aliases 或 Netlify 的 Redirects[8] 来保证原链接不 404。根据我的测试,这两种方式是都可以平滑地实现链接的迁移的,即不会降低文章在 Google 搜索结果中的排名。
#其它方面
在迁移时还会遇到一些小问题,比如:日期格式、修改时间、阅读更多、Hexo 的标签插件等等问题,下面来一一说明。
首先是文章的 Front Matter 中的 date
日期格式的问题。在 Hugo 中,Hexo 默认的日期格式是不能工作的,比如:你必须要修改 1969-07-20 20:17:43
为 1969-07-20T20:17:43+00:00
,即添加了时区的信息。对于这点,你可以用上文提到的那个 Python 脚本批量处理一下。
然后是文章的修改时间的问题。在 Hexo 中,会自动将文件的修改时间作为文章的修改时间,但在 Hugo 中不会。不过你可以自行配置[9],在配置文件 config.toml
中加入 :fileModTime
:
[frontmatter]
lastmod = ["lastmod", ":git", ":fileModTime", ":default"]
如此,就算你没有在文章的 Front Matter 中手动指定修改时间 lastmod
,它依然会随着你的文章的改动或修改而「自动更新」。
在 Hexo 中,你可能会在 Markdown 中使用 Hexo 标签插件来实现一些 Markdown 语法无法实现的特殊排版需求。这些特殊的语法是无法在 Hugo 中生效的,尽管在 Hugo 中有与之对应的短代码,但它们之间的语法是不同的,故我建议最好放弃这种非 Markdown 原生语法的写法。当然,如果你非要手动将之从 Hexo 迁移到 Hugo 中也不是没有可能,比如:你可以用 hugo-notice 实现 NexT 主题的 Note 标签。
此外,对于文章摘要的截取,即「阅读更多」上方的内容。在 Hexo 中你可以在文章中加入 <!-- more -->
来控制,但这在 Hugo 中是不会生效的,在 Hugo 中你必须将空格删除,即 <!--more-->
。
还有一个是 index.md
的问题,在 Hugo 中你必须在它的前面添加一个下划线,即 _index.md
。比如:你想自定义标签页面的标题为中文,那么你先在新建一个 content/tags/_index.md
文件,然后在文件中加入:
+++
title = "标签"
+++
在 Hexo 中,你每对文章进行一次修改,你就必须要在浏览器中手动刷新一下页面,如此才能看到最新的渲染结果🐶。但在 Hugo 中,只要有相关变化,Hugo 就会自动为你刷新页面。也就是说,你可以即时预览😎!顺便安利一个有用的技巧,在配置文件上方添加 newContentEditor = "gedit"
(修改 gedit
为你喜欢的编辑器名),就可以在每次 hugo new
新建文章后自动打开你喜欢的文本编辑器!
在 Hugo 中,与 hexo s
命令等同的是 hugo server
,但由于默认的文章模板里面有 draft: true
,故需加上 -D
参数以渲染草稿;与 hexo g
命令等同的是 hugo
,用来生成 public
文件夹;与 hexo clean
类似的命令是 hugo --gc --cleanDestinationDir
;在 Hugo 中可以直接使用 hugo --minify
压缩 HTML、JS、CSS 等文件;在 Hugo 中,没有 hexo d
一键部署 public
文件夹到 GitHub 的操作,你需要自己新建脚本,但我建议使用 Git 管理整个站点,然后上传整个站点到 GitHub 以通过持续集成的方式部署,而非用 Git 直接部署 public
生产版代码,因为这其实是非常可笑的——Git 是用来管理源码的。此外,关于 hugo
命令的更多参数,请见 Commands。
最后,Hugo 默认是渲染到内存的,如果你有一些特殊情况要调试,比如 Service Worker,那么你可以通过使用 --renderToDisk
参数渲染到硬盘来实现调试。此外,如果你有一个厚重的 static
文件夹(比如有大量图片或视频),那么也可以使用该参数以渲染到硬盘。不然,Hugo 可能会吃光你的电脑内存🤪。
当然,你也可以将它拆分开来,见:https://gohugo.io/getting-started/configuration/ ↩︎
下文的 TOML 代码中的缩进仅为美观和提高代码的可读性。 ↩︎
为了在中文上将 Categories 和 Taxonomies 与广义的分类区分开来,今后一例将 Categories 翻译为「部类」,将 Taxonomies 翻译为「类别」。 ↩︎
需注意的是子文件夹内必须有
_index.md
,否则 Hugo 不会认为该子文件夹为一个分区,见:https://gohugo.io/content-management/sections/#nested-sections ↩︎Hugo 默认的分区的 URL 结构
/:sections/:slug/
就有:sections
变量,但如果你要添加其它变量,则需添加它。 ↩︎即有空格、标点符号,或其它特殊符号。 ↩︎
如果你的博客部署在 Netlify 上的话。 ↩︎
在 Hugo 中,你甚至可以配置 Git 的改动(commit)时间为文章的修改时间,即
:git
。 ↩︎