基于 Hugo + Org Mode + Github Page 的静态站点搭建

简介

现在已经全面转战 Emacs 和 Org Mode 了,想搭个人博客,折腾了很久。这里记录搭建思路和过程,供参考。

工具

  • Emacs + Org Mode
  • Hugo 静态站点生成制作
  • ox-hugo 将 org 文件转换成 md 文件
  • GitHub Page 博客托管平台
  • Gitalk 博客评论,基于 GitHub 的 Issue

搭建流程

假定已经安装了 EmacsGit

安装 Hugo

官方的下载页面在这里 https://gohugo.io/getting-started/installing ,根据自己的系统去安装即可。

sudo pacman -S hugo

Github Page 配置

首先,新建一个仓库,仓库名必须是 <Name>.github.io<Name> 是的 github 的昵称。

如果新建时选了自动建 README.md ,就好了。如果没有执行(里面的 Name 是 github 昵称):

# 随便一个 REMAD.md
echo "# Just a README" >> README.md
git init
git add README.md
git commit -m "first commit"
# 新基础分支
git branch -M main
git remote add origin [email protected]:Name/Name.github.io.git
# 提交到基础分支
git push -u origin main

Settings 里边,在左边那里点开 Page

至此 GitHub Page 就搭建好了,等几分钟它那边部署,然后就可以去访问 https://<Name>.github.io/ 了(<Name> 是 github 昵称),不过现在应该是 404 ,里面什么东西都还没有。

Hugo 使用

新建项目, blog 是项目目录。

hugo new site blog

然后会生成如下的项目:

├── blog
│   ├── archetypes/
│   ├── config.toml
│   ├── content/
│   ├── data/
│   ├── layouts/
│   ├── public/
│   ├── static/
│   ├── themes/

具体作用如下[1]

  • archetypes, 内容模板,可以取参考里面看,暂时不用管。
  • config.toml, 配置文件,详见我的 Hugo 配置
  • content, 内容目录,用于存放 md 源文件。
  • data, 数据模板目录,暂时不用管。
  • layouts, HTML 模板目录,md 生成 HTML 的模板,有一套单独的语言描述。暂时也不用配。
  • public, 发布目录,生成的 HTML 会存到这个目录下边。后面需要修改。
  • themes, 主题目录,用于存放主题相关配置。

测试时,进入 blog 目录,使用:

hugo server

然后会开启默认端口,从输出信息中可以看到。从浏览器访问就能看到。

项目目录修改

克隆仓库:

# blog 下
git clone [email protected]:<Name>/<Name>.github.io.git project

blog/project 目录下新目录存放 org 源码的目录(手动建):

├── project
│   ├── assets/
│   ├── posts/

org 文件就放在 blog/project/posts 下,静态资源就放在 blog/project/assets 下。

因为 Github Page 只能选 / 根目录或者 /docs 目录作为站点目录,所以需要在配置中把站点输出目录修改一下。在我的 Hugo 配置那里我会说的。

Hugo 主题

主题可以去官网上找 https://themes.gohugo.io/ ,我参考的是前辈用的 LoveIt [2],功能强大,界面美观,且不会太花哨。

先找到主题的 github 仓库 https://github.com/dillonzq/LoveIt,然后把它作为 hugo 项目的子模块

# 在 blog 目录下操作
git submodule add https://github.com/dillonzq/LoveIt.git themes/LoveIt

或者直接克隆也可以。

配置信息:基础配置完整配置

复制之后直接贴到 config.toml 即可。

我的 Hugo 配置

配置文件可以说三种类型: toml, yamljson ,我用的是 yaml ,简洁;把 LoveIt 官网给的完整配置转成 yaml ,然后手动调整,参考如下。

因为配置里面可能会用到密钥什么的,因此不建议公开到 GitHub 上边,所以只上传 Org 源码, 静态文件站点文件。如下所示:

├── project
│   ├── assets/   # 存放静态文件
│   ├── posts/    # 存放 org 
│   ├── docs/     # 站点文件

在 GitHub Page 的设置中把分支那里的目录设置成 /docs (只能是 /(root)/docs ),配置文件中修改输出目录 publishDirproject/docs ,以适配 Github Page , public 目录就可以不要了,我把这个设置放到了第一行。

这样源码 org 文件和站点代码都可以被托管到 GitHub 上。

config.yaml

publishDir: project/docs
# 站点URL
baseURL: https://fingerknight.github.io/
# 默认语言
defaultContentLanguage: zh-cn
languageCode: zh-CN
hasCJKLanguage: true
# 主题
theme: LoveIt
# 网站标题
title: 手指骑士的病房
# 作者
author:
  name: Finger Knight
  email: [email protected]
  link: https://fingerknight.github.io/about
# 默认每页列表显示的文章数目
paginate: 12
# 谷歌分析代号 [UA-XXXXXXXX-X]
googleAnalytics: ""
# 版权描述,仅仅用于 SEO
copyright: ""
# 是否使用 robots.txt
enableRobotsTXT: true
# 是否使用 git 信息
enableGitInfo: false
# 是否使用 emoji 代码
enableEmoji: true
# 忽略一些构建错误
ignoreErrors: ["error-remote-getjson", "error-missing-instagram-accesstoken"]

# 标记语言设置
markup:
  highlight:
    noClasses: false
    codeFences: true
    guessSyntax: true
    lineNos: true
    lineNumbersInTable: true
  goldmark:
    extensions:
      definitionList: true
      footnote: true
      linkify: true
      strikethrough: true
      table: true
      taskList: true
      typographer: true
    renderer:
      # 是否在文档中直接使用 HTML 标签
      unsafe: true
  # 目录
  tableOfContents:
    startLevel: 2
    endLevel: 6

# 网站地图配置
sitemap:
  changefreq: weekly
  filename: sitemap.xml
  priority: 0.5

# Permalinks 配置
Permalinks:
  posts: ':filename'

# 输出设置

outputs:
  home:
    - HTML
    - JSON
  page:
    - HTML
    - MarkDown
  section:
    - HTML
  taxonomy:
    - HTML
  taxonomyTerm:
    - HTML

# 语言设置
language:
  zh-cn:
    contentDir: content
    languageName: 中文

# 菜单设置
menus:
  main:
    - identifier: main
      pre: <i class="fa-solid fa-house"></i>
      post: ''
      name: 
      url: /
      title: 主页
      weight: 1
    - identifier: tags
      pre: <i class="fa-solid fa-tags"></i>
      post: ''
      name: ''
      url: /tags/
      title: 标签
      weight: 2
    - identifier: posts
      pre: <i class="fa-solid fa-layer-group"></i>
      post: ''
      name: ''
      url: /posts/
      title: 归档
      weight: 3
    - identifier: about
      pre: <i class="fa-solid fa-bed-pulse"></i>
      post: ''
      name: ''
      url: /about
      title: 病历
      weight: 4


# 参数设置
params:
  # 网站默认主题样式 ["auto", "light", "dark"]
  defaultTheme: "auto"
  # 哪种哈希函数用来 SRI, 为空时表示不使用 SRI
  # ["sha256", "sha384", "sha512", "md5"]
  fingerprint: "sha256"
  # 日期格式
  dateFormat: "2006-01-02"

  # 网站标题, 用于 Open Graph 和 Twitter Cards
  title: "手指骑士的病房"
  # 网站描述, 用于 RSS, SEO, Open Graph 和 Twitter Cards
  description: "痴人说梦"
  # 网站图片, 用于 Open Graph 和 Twitter Cards
  # images:
  #   - /logo.png

  # 首部元素
  header:
    # 桌面端导航栏模式 ["fixed", "normal", "auto"]
    desktopMode: fixed
    # 移动端导航栏模式 ["fixed", "normal", "auto"]
    mobileMode: auto
    title:
      # logo: /avatar.png
      name: 手指骑士的病房
      pre: ''
      post: ''
      typeit: false

  # 尾部元素
  footer:
    enable: true
    custom: ''
    hugo: true
    copyright: true
    # 是否显示作者
    author: true
    # 网站创立年份
    since: 2022
    icp: ''
    # 许可协议信息 (支持 HTML 格式)
    license: <a rel="license external nofollow noopener noreffer" href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">CC BY-NC 4.0</a>

  # Section (所有文章) 页面配置
  section:
    # section 页面每页显示文章数量
    paginate: 20
    # 日期格式 (月和日)
    dateFormat: 01-02
    # RSS 文章数目
    rss: 10

  # List (目录或标签) 页面配置
  list:
    paginate: 20
    dateFormat: 01-02
    rss: 10

  # 主页配置
  home:
    # RSS 文章数目
    rss: 10
    # 主页个人信息
    profile:
      enable: true
      # gravatarEmail: ''
      avatarURL: /img/YunRuAvatar.jpg
      title: ''
      subtitle: 痴人说梦
      typeit: false
      # 是否显示社交账号
      social: false
      # 免责声明
      disclaimer: ''
      # 主页文章列表
    posts:
      enable: true
      paginate: 5
      defaultHiddenFromHomePage: false

  # 文章页面全局配置
  page:
    # 是否在主页隐藏一篇文章
    hiddenFromHomePage: false
    # 是否在搜索结果中隐藏一篇文章
    hiddenFromSearch: false
    # 是否使用 fontawesome 扩展语法
    fontawesome: true
    # 是否在 RSS 中显示全文内容
    rssFullText: false

    prev: false
    next: false
    linktomarkdown: false

    # 开启上下篇
    nav: false

    # 目录
    toc:
      enable: true
      #  是否保持使用文章前面的静态目录
      keepStatic: false
      # 是否侧边目录自动折叠展开
      auto: false

    # 代码
    code:
      # 是否显示代码块的复制按钮
      copy: true
      # 默认展开显示的代码行数
      maxShownLines: 50

    # KaTeX 数学公式
    math:
      enable: true
      inlineLeftDelimiter: ''
      inlineRightDelimiter: ''
      blockLeftDelimiter: ''
      blockRightDelimiter: ''
      copyTex: true
      mhchem: true

    # 地图
    mapbox:
      accessToken: ''
      lightStyle: 'mapbox://styles/mapbox/light-v10?optimize=true'
      darkStyle: 'mapbox://styles/mapbox/dark-v10?optimize=true'
      navigation: true
      geolocate: true
      scale: true
      fullscreen: true

    # 关闭分享
    share:
      enable: false

    # 评论看后面
    # comment:
    #   enable: true
    #   gitalk:

    # 第三方静态资源
    library:
      css: {}
      js: {}

    # 页面 SEO 配置
    seo:
      images: []
      publisher:
        name: ''
        logoUrl: ''

  # TypeIt 配置
  typeit:
    speed: 100
    cursorSpeed: 1000
    cursorChar: '|'
    duration: -1

  # 网站 SEO 配置
  seo:
    image: ''
    thumbnailUrl: ''

  #  第三方库文件的 CDN 设置
  # CDN 数据文件名称, 默认不启用
  # ["jsdelivr.yml"]
  # 位于 "themes/LoveIt/assets/data/cdn/" 目录
  # 可以在你的项目下相同路径存放你自己的数据文件:
  # "assets/data/cdn/"
  # cdn:
  #   data: ''

  # 搜索设置
  search:
    enable: true
    # 搜索引擎的类型 ["lunr", "algolia"]
    type: lunr
    # 文章内容最长索引长度
    contentLength: 4000
    # 搜索框的占位提示语
    placeholder: ''
    # 最大结果数目
    maxResultLength: 10
    # 结果内容片段长度
    snippetLength: 50
    # 搜索结果中高亮部分的 HTML 标签
    highlightTag: em
    # 是否在搜索索引中使用基于 baseURL 的绝对路径
    absoluteURL: false

关于资源目录

假设有个写好了的 md ,位置 content/test.md ,那么它会出现在站点的根目录,也就是需要访问 https://yoursite.com/test.md

同理,静态资源是存在 static 下边的,假如你有网站图标和某个 css 在它下边, static/favicon.icostatic/test.css ,那么访问的时候是 https://yoursite.com/favicon.icohttps://yoursite.com/test.css

所以对于文本中出现的图片,可以新建一个目录 static/assets/ ,放在里面,后续访问就是 https://yoursite.com/assets/xx

安装 ox-hugo

官网的安装页面:https://ox-hugo.scripter.co/#installation

(with-eval-after-load 'ox
  (require 'ox-hugo)
  (setq org-hugo-base-dir ../blog/"
        ;; org-hugo-section "posts"
        org-hugo-default-static-subdirectory-for-externals "img"
        org-hugo-auto-set-lastmod t
        org-hugo-export-with-toc nil))
  • org-hugo-base-dir, 项目的根目录
  • org-hugo-section, 文档的目录,相对于 contnet 而言。如果值是 posts ,它的路径就是 ../blog/content/posts/~ 。这里注释了是因为默认就是 posts
  • org-hugo-auto-set-lastmod, 自动修改“最近更新时间”
  • org-hugo-export-with-toc, 是是否开启文档目录。这个目录放到文章开头, Hugo 配置里面有目录了,所以不用。
  • org-hugo-default-static-subdirectory-for-externals, 静态文件的目录,相对于 static 而言。例如它的值是 img, 那么图片就会存在 ../blog/static/img~

ox-hugo 使用

Org file 的基础配置

#+title: 文章标题
#+date: [2022-09-10 Sat]
#+hugo_tags: tags1 tags2
#+hugo_categories: Class
#+hugo_draft: true

解释如下:

  • title, 文章标题
  • date, 文章创建日期
  • hugo_tags, 标签。空格分隔
  • hugo_categories, 分类
  • hugo_draft, 是否为草稿,如果是的话,不会发布

对于特定文件,可以在文件的头配置里面覆盖掉基础配置。比如“关于”页面 about.org 的内容:

#+hugo_base_dir:../blog/
#+hugo_section: ./
#+hugo_auto_set_lastmod: t
#+hugo_custom_front_matter: :toc false

这样就使得输出的 about.md 直接在 blog/content/ 下面,覆盖了 .emacs 配置里面写的 blog/content/posts

使用命令 C-c C-e H h ,输出当前文件为 md,这是启动 hugo server 就能看到了。

设置评论

LoveIt 主题给出了很多选择,我用的是 Gitalk[3] ,它是基于 Github 的 Issue 模块设计的。它十分太巧妙,用一个 Github 仓库做评论存储 —— 并不是作为 code 来存。对于每个仓库,都有一个 Issue 区域,用来记录问题和反馈。每条 issue 下面都可以评论 —— 本质上 Issue 就是一个评论区,它在 Github 的作用就是反馈问题,用来当博客评论简直舒服(当然,评论者必须登录 GitHub 之后才能评论)。

首先建立一个 Github App,我找不到在哪里,教程里面的 url 直接跳转:Register GiHub Application.

之后可以到个人设置 - Developer Settings - OAuth Apps 里面管理。

建好之后会生成一个 Cilent ID 和一个 Client Secrets ,记着。

然后到 Hugo 的配置里面。如果你用的是我的配置,那么文件就在 params.yaml 里面,如果用的官方单文件配置,找到对应位置即可。

找到评论这一块,关键词 commentenable 设为 true ,表示开启评论。

配置 Gitalk , enable 也设为 true ,然加上几条配置。

  • owner, 仓库拥有者,你的 Github 昵称。
  • admin, 仓库管理者,一个列表,里面也是你的 Github 昵称。
  • repo, 仓库名,指明要用哪个仓库的 Issue 来存评论。我用的和博客同一个仓库,方便自动化,你也可以用其他的仓库。只需要名称,我的某个仓库 fingerknight/some-repo ,那么这里就填 some-repo
  • clientId, clientSecret ,就是刚才的提到的。

注意,这个 Github 仓库一定要是公开的 public ,不然会出现找不到。

第一次使用可能会出现如下情况:

这是因为对应的 Issue 还没建立,点 使用 Github 登录 ,做一些授权就好了。此外,对于新的一篇文章,需要用你带着 Github 的 Cookie 去访问一次,它就会自动建立 Issue,否则,比如其他人看也会出现这个情况。

目前在 LoveIt 那边有提需求说应该把这个自动化,但目前 (2022-09-12) 还没有实现。

LoveIt 的 Bug

如果你有好几篇文章同时生成和发布,那么就有可能出现这个 bug ,会导致评论共用,就是 A 文章下面的评论和 B 文章下面的评论一样, A 文章下面发的评论在 B 文章那里也可以看到。

这个问题是因为二者共用了同一个 Issue ,或者说定位到了同一个 Issue 。Gitalk 是用一个 id 关键字区分不同文章的,你去你的 Issue 可以看到,每个 Issue 都有两个标签,一个是 Gitalk ,一个默认是标准格式日期(我的不是是因为我改了)。前者用来让 Gitalk 知道哪些 Issue 是用来存评论的,哪些是真的问题反馈;后者就是 id ,用来区分不同文章的。

LoveIt 的默认 id 是写死的,就是 Date ,所以如果几篇文章同时发布, Date 就是一样的,那就会定位到同一个 Issue 。

2022-09-11 我已经向项目提了一个 Bug 的 Issue ,等待后续结果。

手动改如下。在 blog/themes/LoveIt/layouts/partials/comment.html 里面边,找到 Gitalk 相关代码:

把图示中 id 后面的 .Date 给改掉。 Gitalk 官网说长度小于 50 ,参照网友的意见用 MD5 摘要,所以我改成 (md5 .File.Path) ,文章相对于 blog/content/ 的相对路径的 MD5 摘要。

工作流程

至此一个博客就搭建好了,接下来说明它的工作流程。

  1. 编写 org 文件。把博客文章的内容写到 org 文件中,记得加头部设置。
  2. 格式化 org 为 md。使用命令 C-c C-e H h 格式化。
  3. 发布成 HTML。把所有要发布的内容格式化成 md 后,可以 hugo server 测试一下,或者直接发布,使用 hugo 就可以看到 project/docs 目录下生成了站点文件。
  4. 最后提交到 Github
git add *
git commit -m "注释"
git push

等一会儿再去访问你的 github.io 应该就好了。

自动化

如果每次都那么搞就太麻烦了。因此我写了一些东西,使得整个流程直接一步到位,可以参考。

地址:https://github.com/fingerknight/org-hugo-lazy.el,这个流程就是基于我上述的搭建过程写的,如果你是完全按照我上面的搭建 Hugo ,那么这个扩展应该很容易上手。

优化

CDN 设置

复制 LoveIt 目录 themes/LoveIt/assets/data/cdn/ 到../blog/assets/data/cdn/~ (assets 需要自己创)[4]

Cloudflare Page

Cloudflare Page 也可以直接托管静态站点了,它是联动 GitHub ,把仓库的 HTML 等站点文件克隆到它的服务器然后部署。参考《几分钟、零基础搭建个人网页!- 高速直连,基于Cloudflare Page - 知乎》。

Cloudflare + DNS

我是自己注册了一个域名,然后用 Cloudflare 托管,具体可以参考我的另一篇博客《GiHub Page + Cloudflare + NameSilo 的域名配置

后记

搞了好几天终于算可以了,留一些尾巴后面处理。

  • [X] 评论功能
  • [X] CDN 加速
  • [X] 样式调一下,这个主题整体显得比较挤,还有字体。如果有精力把配色也改一下
  • [ ] 还有就是 SEO 、挂友链,挤进搜索引擎,增加曝光度,这个貌似 LoveIt 已经帮忙调好了。

希望对你有帮助!

Powered by Org Mode.