使用 Emacs + org-mode + ox-hugo 搭建静态博客
前言
如果你是 Emacs 用户,一定对 org-mode 这个强大的文本组织模式有所耳闻,本文将详细介绍如何结合 org-mode 与静态网站生成器 Hugo 来搭建一个极其高效且简单的博客创作流程,内容包括:
- Hugo 的安装与配置
- ox-hugo 的配置
- 写作与发布流程
- 自定义博客首页
- 快速部署
- 常见问题解决
想跟随本文搭建博客,你可能需要:
- 能进行简单的 Emacs 配置和 org-mode 使用,了解如何安装需要的插件
- (非常)基础的 git 与命令行经验
环境准备
1. 安装 Hugo
Hugo 是用 Go 语言编写的静态网站生成器,以速度著称。
以 Windows 平台为例,使用 Scoop 安装:
scoop install hugo-extended
安装完成后验证:
hugo version
看到类似输出便安装成功(注意要有 +extended):
hugo v0.157.0+extended windows/amd64
2. 创建 Hugo 站点
cd your-blog-directory
hugo new site your-blog-name
cd your-blog-name
3. 安装主题
此处以简洁快速的 nostyleplease 主题演示:
git clone https://github.com/hanwenguo/hugo-theme-nostyleplease.git themes/nostyleplease
修改 hugo.toml 启用主题:
baseURL = 'https://yourdomain.com'
languageCode = 'zh-cn'
title = 'your blog name'
theme = 'nostyleplease'
[markup.goldmark.renderer]
unsafe = true
最后两行用于允许不安全 HTML , 例如换行标签。
Emacs 配置
1. 安装 ox-hugo
ox-hugo 是一个将 org-mode 导出为 Hugo 兼容的 Markdown 的工具。
在 Emacs 配置中使用 use-package 或其他包管理器安装:
(use-package ox-hugo
:ensure t
:after ox)
2. 创建 org 文件结构
在项目根目录或新建目录中创建 all-posts.org ,或其他任意名称的 .org 文件:
#+hugo_base_dir: your-absolute-path
#+hugo_auto_set_lastmod: t
#+OPTIONS: \n:t
* Blog Ideas
** TODO 第一篇博客
:PROPERTIES:
:EXPORT_FILE_NAME: first-post
:END:
这是第一篇博客的内容...
配置说明:
#+hugo_base_dir:- 指定 Hugo 站点的根目录
#+hugo_auto_set_lastmod: t- 自动添加最后修改时间
#+OPTIONS: \n:t- 保留换行符
EXPORT_FILE_NAME- 导出的文件名(不带 .md 后缀)
CLOSED- 文章发布时间(使用 org 的 TODO 状态切换自动生成, DONE 即为发布)
3. 添加 org capture 模板(推荐)
在你的 Emacs 配置文件中添加:
;; Populates only the EXPORT_FILE_NAME property in the inserted heading.
(with-eval-after-load 'org-capture
(defun org-hugo-new-subtree-post-capture-template ()
"Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
(let* ((title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(
,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_FILE_NAME: " fname)
":END:"
"%?\n") ;Place the cursor here finally
"\n")))
(add-to-list 'org-capture-templates
'("h" ;`org-capture' binding + h
"Hugo post"
entry
;; It is assumed that below file is present in `org-directory'
;; and that it has a "Blog Ideas" heading. It can even be a
;; symlink pointing to the actual location of all-posts.org!
(file+olp "all-posts.org" "Blog Ideas")
(function org-hugo-new-subtree-post-capture-template))))
将最下方的 all-posts.org 替换为你的博客绝对路径, Blog Ideas 任意更改。
然后打开 org-capture ,便可以看到新添加的 Hugo post 选项。
更多教程参考 ox-hugo 官方文档相关章节
写作流程
1. 新建博客
在 org 文件中添加新的 TODO 项:
** TODO 文章标题
:PROPERTIES:
:EXPORT_FILE_NAME: article-slug
:END:
文章正文...
或使用刚刚配置的 capture 模板。
2. 发布文章
当文章完成后,将 TODO 改为 DONE:
** DONE 文章标题
CLOSED: [2026-03-15 Sun 17:30]
:PROPERTIES:
:EXPORT_FILE_NAME: article-slug
:END:
使用快捷键 C-c C-e H H 手动导出当前文章。
3. 自动导出
在与 all-posts.org 相同的目录下添加 .dir-locals.el 文件,在文件中粘贴:
((org-mode . ((eval . (org-hugo-auto-export-mode)))))
即可在保存文件时自动导出 markdown 。
详细配置参考 ox-hugo 官方文档相关章节
4. 本地预览
在终端中运行:
hugo server
访问对应地址实时预览效果。
如果要预览未发布(状态为 draft/TODO )的笔记,可以使用:
hugo server -D
样式定制
自定义首页布局
这是一个简单的首页模板,创建 layouts/index.html 可以覆盖主题默认:
{{ define "main" }}
<style>
.post-list {
list-style: none;
padding: 0;
}
.post-item {
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
.post-summary {
padding: 10px;
}
.post-meta {
color: #666;
font-size: 0.9em;
}
</style>
<ul class="post-list">
{{ range (where .Site.Pages "Section" "posts") }}
{{ if not .IsNode }}
<li class="post-item">
<details>
<summary>
<strong><a href="{{ .Permalink }}">{{ .Title }}</a></strong>
<span class="post-meta"> — Date: {{ .Date.Format "2006-01-02 15:04" }} | Lastmod: {{ .Lastmod.Format "2006-01-02 15:04" }}</span>
</summary>
<div class="post-summary">
{{ if .Summary }}
{{ .Summary }}
<p><a href="{{ .Permalink }}">More →</a></p>
{{ else }}
<p><a href="{{ .Permalink }}">More →</a></p>
{{ end }}
</div>
</details>
</li>
{{ end }}
{{ end }}
</ul>
{{ end }}
数学公式支持
如果 LaTex 因某些原因无法正常渲染,需要添加 shortcode 配置。
创建 shortcode 文件 layouts/shortcodes/texi.html :
<span class="math inline">\( {{ .Inner }} \)</span>
和 layouts/shortcodes/texd.html :
<div class="math display">\[ {{ .Inner}} \]</div>
在 org 中使用:
行内公式: {{< texi >}} \varphi {{< /texi >}}
显示公式:
\[ \]
在线部署
准备仓库
将 hugo 根目录初始化为 git 仓库,该仓库用于管理源码, push 后再新建一个仓库,新仓库用于部署,必须设为公开(除非升级 GitHub Pro)。
如果将新仓库的仓库名设为 用户名.github.io ,部署后的博客域名便没有仓库名后缀。
由于 hugo 默认不会清理 public/ 目录,推荐将其添加到 gitignore 中,避免上传已删除的博客。
生成 Token
- 点击 github 头像 -> Settings -> Developer settings -> Personal access tokens -> Tokens(classic) -> Generate new token(classic)
- 勾选所有 repo 相关
- 复制生成的 token (该 token 只显示一次)
配置 Actions
在私有仓库中
- 点击 Settings -> Secrets and variables -> Actions -> New repository secret
- Name 填任意名称,此处以
PERSONAL_TOKEN为例 - Secret 粘贴刚刚生成的 token -> Add secret
配置好 token 后,在私有仓库根目录创建 .github/workflows/deploy.yml
name: Deploy Hugo
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
personal_token: ${{ secrets.PERSONAL_TOKEN }}
external_repository: 你的用户名/你的用户名.github.io
publish_branch: main
publish_dir: ./public
force_orphan: true
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
其中, user_name 和 user_email 用于以 bot 身份进行提交,避免不必要的个人 commit 记录和贡献统计,推荐保留。
注意替换你的实际 token 名称与 github 用户名。
触发部署
将这个 workflow 文件 push 到仓库中后,可以在 Actions 中查看运行状态。
成功后点击 Settings -> Pages 查看链接,点击即可访问,此后只需修改私有仓库便会自动部署。
一键发布或预览
如果想要更加便捷地发布博客,可以在 Emacs 配置中添加:
(defun my-blog-publish ()
"publish blog , git add/commit/push , generate commit message"
(interactive)
(save-buffer)
(let* ((blog-dir "你的 git 仓库绝对路径")
(timestamp (format-time-string "%Y-%m-%d %H:%M:%S"))
(default-directory blog-dir))
;; git add
(message "Adding files...")
(eshell-command "git add .")
;; git commit with timestamp
(message "Committing: %s" timestamp)
(eshell-command (format "git commit -m \"Update: %s\"" timestamp))
;; git push
(message "Pushing to remote...")
(let ((exit-code (eshell-command "git push")))
(if (eq exit-code t)
(message "Push may have failed or nothing to push, check *Messages* buffer for details")
(message "Push successfully %s" timestamp)))))
(global-set-key (kbd "你想要的快捷键") 'my-blog-publish)
使用此命令便自动生成 commit 信息并发布博客。
同样,添加这段配置可以自动打开浏览器进行本地预览:
(defun my-blog-preview ()
(interactive)
(save-buffer)
(let ((default-directory "你的 hugo 仓库绝对路径"))
;; 启动 hugo server
(start-process "hugo-server" "*hugo-server*"
"hugo" "server" "-D" "--bind" "0.0.0.0" "--port" "1313")
;; 等待服务器启动
(sleep-for 2)
;; 自动打开浏览器
(browse-url "http://localhost:1313")))
(global-set-key (kbd "你想要的快捷键") 'my-blog-preview)
如果想结束预览,关闭 *hugo-server* buffer。
常见问题
文章不显示
检查:
- 是否设置了
CLOSED时间戳 - 是否将
TODO改为DONE - 是否成功导出
主题中的示例文章
主题目录下可能有 content/posts/ ,删除其中的示例文件。
lastmod 不更新
确保 org 文件中有:
#+hugo_auto_set_lastmod: t
并且每次导出前保存文件。
总结
这套工作流的优势:
- 高效文本写作 :: 使用 org-mode,专注内容
- 版本控制友好 :: 便于 git 管理
- 自动化 :: 自动生成时间戳、目录、RSS 等
- 部署便捷 :: 更新仓库自动部署
- 可定制 :: Hugo 模板完全可自定义
- 快速 :: Hugo 生成速度极快,秒级构建,实时预览
Happy Blogging!