使用 Hugo 和 GitHub Pages 搭建静态博客

作为一个已经入行了一年多的(老)技术人,维护一个看得过去的个人博客是很有必要的。

刚学编程的时候,还开源过一个基于 Flask 和 MongoDB 的博客项目,但后面就没怎么使用和维护了,除开主观上的懒,还因为:

  • 自建博客绕不开主机和域名,这是一笔持续的经济成本。国内的主机往往是第一年割肉第二年宰猪,国外的主机访问延迟很高。
  • 国内主机更麻烦的是还需要定期备案,第一个博客就是因为备案到期中断了。
  • 博客功能的实现技术难度不大,开始还有一些新鲜感,有了工作经验后就很难有兴趣继续维护了。

刚好最近有写一些文章的打算,决定找个简单、省事(最后发现并没有)且不花钱的路子把博客再搞起来,一番研究后,选择了生成静态站点发布到 GitHub Pages 的方案。

工作流

整个方案的流程大致如下:

  1. 用 Markdown 格式写作文章。
  2. 使用生成器将 markdown 文件转换成静态站点。
  3. 将生成的站点内容推送到 GitHub 并发布。

写 markdown 没啥好说的,什么编辑器都可以,我一直用的是 Typora。

静态站点生成器我选择了 Hugo,原因是最近刚好在学 Go,此外还有 Gatsby、Jekyll、Hexo等很多选项。

接下来要做的工作是生成静态站点并通过 GitHub Pages 发布。

生成静态站点

使用 Hugo 生成静态博客站点非常简单,具体的步骤和用法可以参考官方文档的 Quick Start。下面简单介绍下整个过程:

  1. 安装 Hugo。macOS 下可以直接使用 homebrew安装:brew install hugo

  2. 创建一个新的站点。这会生成一个特定目录结构的项目文件夹,用来维护所有的站点内容。假设我们想把它命名为 hugo-blog,则使用以下命令创建并切换到该目录,后续的操作和命令都会在这个根目录下执行:

    hugo new site hugo-blog
    cd hugo-blog
    
  3. 安装一个主题。这一步是必需的,否则会因为缺少基础模板无法生成站点。安装主题有 3 种方式,以 eureka 主题为例:

    • 直接下载主题的压缩文件,将解压得到的文件夹重命名为主题名称 eureka 放到 themes/目录下。

    • 通过 git submodule 安装:

      git init
      git submodule add https://github.com/wangchucheng/hugo-eureka.git themes/eureka
      
    • 通过 Hugo Modules 安装(这种方式要求本机安装有 Go 1.12 及以上版本,且只有部分主题支持):

      hugo mod init <module_name>
      

      <module_name> 并不重要,随便起个名字就行。

    安装后需要启用主题,方法是将主题名称写入到根目录下的默认配置文件 config.yml 中:

    echo 'theme = "eureka"' >> config.toml
    

    如果是通过 Hugo Modules 安装,需要把主题名称替换成模块名称:

    echo 'theme = "github.com/wangchucheng/hugo-eureka"' >> config.toml
    
  4. 添加一篇文章。可以直接在 content/posts 目录下创建 markdown 文件,但需要手动写入一些元信息,因此推荐使用 Hugo 自带的命令:hugo new posts/my-first-post.md,添加的文件会以如下元信息开头:

    ---
    title: "My First Post"
    date: 2019-03-26T08:47:11+01:00
    draft: true
    ---
    

    在下方接着写入文章内容即可。注意此时该文件为草稿状态,写作完成后需要改成 draft: false 才能部署。

  5. 启动 Hugo 预览服务器。Hugo 可以启动一个 Web 服务器,同时构建站点内容到内存中并在检测到文件更改后重新渲染,方便我们在开发环境实时预览对站点所做的更改。

    hugo server -D
    

    添加 -D 选项以输出草稿状态的文章,执行成功后可以通过 http://localhost:1313/ 访问站点。

  6. 自定义主题配置。站点的配置项默认保存在根目录的 config.toml 文件中,配置项较多时通常会用主题提供的预设配置文件来替换该文件,还可以通过config 目录加多个文件的方式来组织配置。默认配置文件如下:

    baseURL = "http://example.org/"      # 发布地址,由主机名以及路径组成
    languageCode = "en-us"               # 语言代码,中文可以设置为"zh"
    title = "My New Hugo Site"           # 站点标题
    

    这一步应该是整个过程中最麻烦也是最容易出问题的一步,视乎你选择的主题与想要的功能不同,需要自定义的配置项也不同,数量从几个到上百个不等。有些主题会有详细的文档解释配置过程,有些则一笔带过只能自己去摸索,配置较多时相互间可能还有依赖关系,最好更改一个配置就刷新一次页面确认下结果。

    建议起步时一切从简,花大把时间搞各种花里胡哨的样式和功能,还不如多写几篇文章。

  7. 构建静态页面。站点配置成我们理想的效果之后就可以构建静态页面了:

    hugo -D
    

    添加 -D 选项可以在结果中包括草稿内容,默认情况下静态页面会输出到根目录下的 public 文件夹中。

通过 GitHub Pages 发布

这一步 Hugo 的官方文档同样在 Host on GitHub 中进行了详细的介绍,并且还很贴心的提供了自动化操作的 Shell 脚本。

有两种方式:

  1. 通过个人主页发布:必须创建一个 <USERNAME>.github.io 仓库来托管生成的静态内容,发布后的域名为 https://<USERNAME>.github.io

  2. 通过项目主页发布:可以随意创建 <PROJECT_NAME> 仓库,发布后的域名为 https://<USERNAME>.github.io/<PROJECT_NAME>

视选择的发布方式不同,我们需要将 config.yml 中的 baseUrl 设置为不同的值。

通过个人主页发布

建议非特殊情况下使用第 1 种方式,原因是许多主题都不能很好的支持第 2 种,具体来说是将 config.tomlbaseURL 设置为含子路径的地址时,不能正确的处理所有资源的构建位置。我尝试了 3 个主题,均遭遇了不同的问题:

  • eureka: 构建失败,提示 :

    Error: Error building site: POSTCSS: failed to transform "css/eureka.css" (text/css): resource "css/waynerv.github.io/css/eureka.css_fc3f76d7bee2760c3a903059afc3d9b2" not found in file cache
    
  • LoveIt: 构建成功,但除主页以外的文章、分类和标签的页面均提示 404。

  • MemE: 构建成功,但文章中插入的图片加载 404(放在同一文件夹的favicon 却能正常展示)。

这个问题我在 eureka 项目提交了 issue,开发者回复可能是 Hugo 本身的机制所导致,并已经在 Hugo 论坛中提出了此问题,有兴趣的可以关注后续进展。

发布步骤

  1. 在 GitHub 创建个人主页仓库,仓库名称必须设置为 <USERNAME>.github.io,这个仓库仅存放生成的静态内容。

  2. 在 GitHub 创建一个项目仓库 hugo-blog 并添加为我们本地项目文件夹的远程仓库。这个仓库用来维护站点配置和原始的文章内容。

  3. 假设我们在已经通过上文的步骤在 public 文件夹中生成了想发布的静态内容,运行:

    git submodule add -b main https://github.com/<USERNAME>/<USERNAME>.github.io.git public
    

    public 目录中创建一个 git 子模块,之后这个目录将以 https://github.com/<USERNAME>/<USERNAME>.github.io 作为远程仓库。

  4. 确保配置文件中的 baseUrl 已经设置为了 <USERNAME>.github.io

  5. Hugo 为我们接下来的部署操作提供了一个自动化的 Shell 脚本:

    #!/bin/sh
    
    # 任一步骤执行失败都会终止整个部署过程
    set -e
    
    printf "\033[0;32mDeploying updates to GitHub...\033[0m\n"
    
    # 构建静态内容
    hugo # if using a theme, replace with `hugo -t <YOURTHEME>`
    
    # 切换到 Public 文件夹
    cd public
    
    # 添加更改到 git
    git add .
    
    # 提交更改
    msg="rebuilding site $(date)"
    if [ -n "$*" ]; then
    msg="$*"
    fi
    git commit -m "$msg"
    
    # 推送到远程仓库
    git push origin main
    

将如上内容保存到 deploy.sh 文件中,并执行 chmod +x deploy.sh 为其添加可执行权限。接着执行部署脚本:

./deploy.sh

大功告成!稍等几分钟就可以在 https://<USERNAME>.github.io 看到我们的个人博客了。

通过 GitHub Actions 自动部署

目前我们的「创作-发布」流程如下:

  1. 在项目仓库编辑原始内容并进行版本管理。

  2. 执行自动脚本生成静态站点并推送到个人主页仓库完成发布。

这套流程已经很流畅,但还有一些改进空间:我们可以使用 GitHub Actions,在每次向远程的项目仓库推送原始内容更改时自动执行第 2 步进行发布。

GitHub 上有许多这类自动化部署任务的开源 Actions 项目,我们选择了其中一个简单易用的 GitHub Actions for Hugo。具体的操作步骤截图和详细配置项可以查看该项目的 README。下面简单介绍下配置过程:

  1. 在项目文件夹中添加目录和文件:.github/workflows/gh-pages.ymlgh-pages.yml 文件内容如下:

    name: github pages
    
    on:
      push:
        branches:
          - main  # 每次推送到 main 分支都会触发部署任务
    
    jobs:
      deploy:
        runs-on: ubuntu-18.04
        steps:
          - uses: actions/checkout@v2
            with:
              submodules: true  # Fetch Hugo themes (true OR recursive)
              fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod
    
          - name: Setup Hugo
            uses: peaceiris/actions-hugo@v2
            with:
              hugo-version: '0.79.1'
              extended: true
    
          - name: Build
            run: hugo --minify
    
          - name: Deploy
            uses: peaceiris/actions-gh-pages@v3
            with:
              deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
              external_repository: <USERNAME>/<USERNAME>.github.io  # 替换成上文所创建的个人主页仓库
              publish_branch: main
              publish_dir: ./public
    

    这个文件所定义的 workflow 基于项目仓库运行,但我们需要将运行过程生成的静态文件推送到个人主页仓库 <USERNAME>.github.io 完成发布,因此在 Deploy 任务中按照文档的 Deploy to external repository external_repository 一节做了专门的配置。

  2. 在本地生成 SSH 部署密钥:

    ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""
    # 将在当前目录生成如下密钥文件:
    #   gh-pages.pub (公钥)
    #   gh-pages     (私钥)
    
  3. 在 GitHub 分别进入项目仓库和个人主页仓库的 Settings 页面:

    • 将公钥 gh-pages.pub 作为 Secret 添加到项目仓库,并设置 NameACTIONS_DEPLOY_KEY

    • 将私钥 gh-pages 作为 Deploy Key 添加到个人主页仓库,并设置为 Allow write access

接下来我们测试一下效果。

在本地做一些更改,预览效果后提交并推送,然后在项目仓库的 GitHub Actions 页面检查相应的 workflow 是否运行成功。不出意外的话,很快个人主页仓库将新增一个由该 workflow 创建的提交,访问个人博客页面也会发现页面已经更新。

个人体验

由于先后选择的 3 个主题均遭遇了上述发布地址不能包含子路径的问题,我在基本按照官方文档操作的前提下,依然花了超过 10 个小时才把博客上线,浪费了很多时间在配置主题以及寻找问题的解决方案上。本以为选择了一个简单快捷的省心方案,结果还是免不了过程的一顿踩坑和折腾。

虽然搭建博客的流程不算省心,但我所遇到的这些问题也算是个例。一切准备就绪后,我们可以像写代码一样写博客,对文章修改提交即自动发布,也不需要考虑博客的样式、后台功能及主机维护等问题,对提升写作效率会有所帮助,可以省下来很多的时间和精力,综合来看体验还是不错的。

updatedupdated2022-08-032022-08-03