前言
最近花了将博客从notionnext转移到hugo。
转移的理由有以下:
- notionnext需要在notion上写作,但众所周知,由于GFW的存在,写作体验不能说良好,不如本地
- notionnext是依靠notion的api获取数据库的内容再进行渲染,而之前就因为接口更改、个人没能及时更新仓库的原因导致图片无法加载出来,一度以为个人是notion上的图片都炸了。
- hugo有这非常活跃的社区,且主题非常多,涵盖面非常更广;notionnext虽然也有非常多的主题,但还是比不上hugo的社区丰富。
- 自用的obsidian和notion有一小部分的语法冲突。
纯粹就是闲到蛋疼。
综上所述,为了摆脱对我而言不怎么便利notion写作和较为丰富的社区主题,便是我从notionnext迁移到hugo的原因。
那么接下来讲述的目标就很清晰了:
- 部署hugo博客。
- 将文章从notionnext转移到hugo。
部署hugo博客
我们分以下三步来进行hugo博客的部署:
- 理解hugo的工作原理。
- 安装hugo。
- 将博客运转在服务器上。
hugo的本质
想要真正部署好hugo,首先需要知道hugo到底是什么。
hugo和notionnext一样,本质上都是一个静态网页生成器。hugo一般是在自己电脑上运行生成网页文件;而notionnext则是已经写好程序和网页,可以在云端进行实时的信息获取、生成网页内容并运行。
因此hugo需要在本地生成静态网页文件后,还需要将这些放在一个web服务器上才能将博客搭建起来,无论什么修改都需要进行生成“网页文件-将文件推送到web服务器这么一个流程”;notionnext则不需要这么麻烦,只需要写文章笔记即可,上述流程会由服务器自动完成。
hugo部署
hugo比起notionnext要“干净”,是不含主题文件的,需要额外下载。
hugo本体
hugo比起notionnext要“干净”,本体只是一个由命令行操控的静态网页生成器。
来到hugo的github,选择一个符合本地系统的包下载。
注意,一定要是本地系统,不是服务器。如果是Windows就下载Windows,Linux就下载Linux。除非搭建、写好博客、生成网页这几个环节不出错且不需要预览,否则务必请下载适合本地系统运行的程序,不然会出现“顺利生成预览网站但就是进不去,网站无法连接”的情况。我在这里撞墙了好久才弄明白,hugo生成的预览静态网站,默认使用的域名是localhost。因此必须要使用本地系统来运行hugo。这就是我碰到的第一个大坑,害我白白浪费一小时。
进入到hugo程序所在的文件,生成一个新的站点:
hugo new site blog
hugo程序所在的文件夹会就地生成一个blog文件夹,也就是hugo项目的根目录。将hugo的程序复制一份到blog文件夹中,后续的操作全部都在blog文件夹中。
hugo主题
我们可以来到Hugo Themes中下载一个喜欢的主题。我目前使用的是Github Style风格。

因此就用这个主题作为演示,想要部署其他主题的用户可以结合作者教程去部署。
到 blog/themes 中下载主题文件。
git clone https://github.com/MeiK2333/github-style.git
编辑hugo配置
基本每个hugo主题都会自带一个预设hugo.toml,可以根据自身需求去配置hug。Github Style则是themes/github-style/config.template.toml。将其复制到项目根目录并重命名为hugo.toml。
个人使用快速启动配置:
baseURL = "your blog domain" # 博客地址,填入后续搭建真实的地址
languageCode = "zh-cn"
title = "your blog name" # 博客名称
theme = "github-style" # 博客选择,默认不动
pygmentsCodeFences = true # 代码区域高亮
pygmentsUseClasses = false # true:使用css来配置代码高亮;false:使用内联样式配置代码高亮。需要 pygmentsCodeFences 为 true
[params] # 博客主页个人介绍以及个性化配置
author = "author" # 作者
description = "personal description" # 个人描述
github = "your github" # 个人github账户名
twitter = "your twitter" # 个人推特账户名
email = "your email" # 个人邮箱
facebook = "your facebook" # 个人脸书
linkedin = "your linkedin" # 个人领英
instagram = "your instagram" # 个人instagram
tumblr = "your tumblr" # 个人tumblr主页
url = "domain" # 个人主页,建议放入 baseURL 相同的链接
keywords = "blog, google analytics" # SEO搜索引擎关键词,可注释
rss = true # rss订阅
lastmod = true # 处理文章生成时间
favicon = "/images/github-mark.png" # 网站图标,即浏览器
avatar = "/images/avatar.png" # 个人头像
headerIcon = "/images/github-mark-white.png" # 网站抬头
location = "China" # 地区
userStatusEmoji = "🤣" # 头像旁边的emoji
enableGitalk = false # gitalk评论功能关闭
enableSearch = true # 搜索功能启用
[outputs] # 本地搜索相关配置
home = ["html", "json"]
[outputFormats.json] # 本地搜索相关配置
mediaType = "application/json"
baseName = "index"
isPlainText = false
[params.gitalk] # gitalk相关配置,需要将 enableGitalk 设置为 true
clientID = "client ID" # Your client ID
clientSecret = "client secret" # Your client secret
repo = "Repository name" # 博客的github地址Repository name,即仓库名。用于生成作用于评论的issue
owner = "your github" # 您的GitHub ID
admin = "your github" # 您的GitHub ID
id = "location.pathname" # 文章页面的链接地址就是ID
labels = "gitalk" # Github issue labels.
perPage = 100 # 可评论数量,最大为100
pagerDirection = "last" # 时间倒序来排列评论
createIssueManually = true # 设置为true,如果是管理员登录,会自动创建issue,如果是false,需要管理员手动添加第一个评论(issue)
distractionFreeMode = false # 如果启用,则输入 (cmd|ctrl + enter) 可以快速回复
proxy = "https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token" # 为不方便登录github的读者通过反向代理,可自行搭建
[frontmatter]
lastmod = ["lastmod", ":fileModTime", ":default"] # 文章时间处理配置
[services]
[services.googleAnalytics]
ID = "UA-123456-789" # googleAnalytics id
favicon、avatar、headerIcon需要放在themes/github-style/static/images中,并在hugo.toml改成相对应的文件名。需要注意,图像需要为像素为100整数倍的正方形图像,例如300×300、700×700。
写完hugo.toml后,在项目根目录进入cmd,然后输入hugo server -D,浏览器访问http://localhost:1313便可进入到hugo生成的预览网站,针对hugo做出的任何修改都可以得到实时的反馈。
到content文件夹中新建一个readme.md,这边便是个人详细介绍,可以随意填写,比如写上自己的各种主页
### Links
- bilibili:
- github:
- email:
hugo博客发布
前文也已经写了,hugo本质上是一个静态网页生成器,我们需要将它生成的静态网站文件发布到类似nginx之类的web服务器或者诸如github page之类的网站托管平台上。个人还是推荐优先选择github page,对于日后回档、备份等非常方便,它已经救了我好几次了。
在项目根目录进入cmd,输入hugo,便可以在public文件夹中找到编译好的网站文件。直接全部上传到web服务器或者网页托管平台即可成发布。
如果发布后出现css丢失而本地预览时一切正常,即成功发布的网站上只有文字没有样式,大概率是hugo.toml中的baseURL写错了,需要写上博客真实的地址。修改完成后再重新编译、上传发布。
博客文章的编写
编写之前需要进行文章的预设进行修改。可以根据themes/archetypes/default.md修改archetypes/default.md里面的内容。
由于个人使用的是obsidian进行markdown的编写,使用yaml语法写的预设放在obsidian正好就是笔记属性模块,因此我更喜欢写成这样:
---
date: {{ .Date }}
draft: true
title: {{ replace .File.ContentBaseName "-" " " | title }}
summary:
tags:
---
draft为true是,文章处于草稿状态,不会在网站中显示。
在项目根目录进入cmd,输入
hugo new post/<文件名称>.md
在content/post便会生成一个带着预设的markdown文件,需要在这个文件上进行博客文章的撰写。使用hugo生成静态网站文件前也需要将博客文章放入到该文件夹中。
图片附件的引用
图片附件的文件存放
如果跟我一样是使用obsidian进行markdown编辑,那么需要进行一番设置,不然就会跟我一样掉入到第二个大坑。
obsidian对于图片等附件,默认的做法是直接塞到仓库的根目录下;而hugo则需要将引用的图片放到项目的 static/images 目录下,这与Obsidian的默认存放方式不同,直接迁移会不方便。
而且obsidian对图片应用默认使用它所谓的WIKI链接,即![[图片名]]的格式,而不是markdown中标准的url ![]() 格式,而hugo则不认WIKI链接格式。
综上所诉,使用obsidian写文章时需要进行一些设置:
- 点击左下角齿轮图标进入,选择“文件与链接”,将”内部链接类型“改为”基于当前笔记的相对路径“。
- 关闭”使用WIKI链接“。
- ”附件默认存放路径“改为”指定的附件文件夹“,并将”附件文件夹路径“写入
images。
这样,所有图片都会进入到obsidian中的images文件夹中,后续挪动图片到hugo时,只需要将images文件夹放入到hugo项目中的static文件夹即可。
如果觉得所有的博客的图片都放在一个文件夹中还是显得混乱,那么可以选择安装obsidian的Custom Attachment Location插件。它能够接管所有的markdown文件关于附件重命名、存放位置并实时编写markdown url格式,且提供相当的自定义功能。
我个人对于图片附件的存放预期是在images文件夹中建立一个跟markdown笔记同名的文件夹,该笔记的图片附件通通放入其中,并根据放入时间进行重命名。因此我的对Custom Attachment Location进行了如下配置:
- ”新附件位置“写入
./images/${noteFileName}。 - ”生成的附件文件名“写入
${date:YYYYMMDDHHmmss}。 - ”markdown url格式“写入
images/${noteFileName}/${originalAttachmentFileName}.${originalAttachmentFileExtension}。 - ”附加重命名模式“改为”全部“。
这样设置后,图片和其他附件会存储在obsidian仓库的根目录/images/<文章笔记同名文件夹>,而且会根据时间来重命名所有附件。
图片附件引用的url格式修改
如果此时兴冲冲的就运行hugo预览网站,那么大概率的结果是——文章的图片无法正常引用。而这,也是我碰到的第三个大坑。
经过大半天的研究,我意识到还是hugo本质是一个静态网站生成器的问题,貌似我踩的每一个坑在某种程度上都是因为这个原因。它的一个功能是将markdown文件转换成静态网站所需的html文件,因此生成的博客文件对于图片的链接是:https://<博客域名>image/<图片>,而正确的链接格式应为:https://<博客域名>/images/<图片>。问题在于原链接在 images 前少了一个斜杠/。
现在需要做的事情就很明显了:在所有的markdown中,图片的引用链接从改为。不过一个一个的改也显得太费人工了,对付这种有着明显特征的重复性劳动,python脚本显然是一个更好的选择。我们只需要将:
print("开始精确替换图片路径...")
print(f"处理目录: {os.getcwd()}")
print()
file_count = 0
total_replacements = 0
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 精确替换:只替换 
if content != new_content:
# 计算替换次数
replacements = content.count(old_pattern) - new_content.count(old_pattern)
# 直接保存,不备份
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
total_replacements += replacements
file_count += 1
print(f"✓ 已处理: {file} ({replacements} 次替换)")
except Exception as e:
print(f"✗ 处理文件失败: {file} - {str(e)}")
print()
print("处理完成!")
print(f"共处理了 {file_count} 个文件")
print(f"总共进行了 {total_replacements} 次替换")
if total_replacements == 0:
print("\n提示: 没有进行任何替换,可能是因为:")
print("1. 文件中没有找到 '
print("2. 文件编码问题")
print("3. 图片链接已经是指定格式")
input("按回车键退出...")
if __name__ == "__main__":
precise_replace()
上述脚本需要放在content文件夹中,每次修改了博客文章都需要运行一次,它会自动检测修改content及其子文件夹中的markdown文件。
notionnext博客迁移
notionnext对笔记的格式跟hugo非常类似,基本上导出就可以直接放到obsidian正常读取。但notionnext导出的笔记里内容会带一个标题和笔记属性。先要将多余的笔记属性和标题删除可以使用下面这个python脚本:
import os
import glob
def process_markdown_files():
"""
处理当前目录及子目录下所有.md文件
"""
# 获取当前目录
current_dir = os.getcwd()
# 查找所有.md文件(包括子目录)
md_files = glob.glob(os.path.join(current_dir, "**", "*.md"), recursive=True)
# 定义需要删除的关键字
keywords = ['type', 'status', 'slug', 'summary', 'tags', 'category']
processed_count = 0
for file_path in md_files:
try:
# 读取文件内容
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
# 如果文件行数不足,跳过处理
if len(lines) < 3:
print(f"跳过文件 {file_path},行数不足")
continue
# ① 删除第一行和第二行
lines = lines[2:]
# ② 检查新的前6行(原文件的第3-8行),删除包含关键字的行
lines_to_check = min(6, len(lines)) # 确保不超过文件行数
indices_to_remove = []
for i in range(lines_to_check):
line = lines[i].strip() # 去除首尾空白字符
# 检查行开头是否包含关键字(忽略大小写)
if any(line.lower().startswith(keyword) for keyword in keywords):
indices_to_remove.append(i)
# 从后往前删除,避免索引变化问题
for index in sorted(indices_to_remove, reverse=True):
del lines[index]
# ③ 再删除一行(新的第一行)
if len(lines) > 0:
lines = lines[1:]
# 写回文件
with open(file_path, 'w', encoding='utf-8') as file:
file.writelines(lines)
processed_count += 1
print(f"已处理文件: {file_path}")
except Exception as e:
print(f"处理文件 {file_path} 时出错: {str(e)}")
print(f"\n处理完成!共处理了 {processed_count} 个.md文件")
if __name__ == "__main__":
# 确认操作
confirm = input("此脚本将修改当前目录及子目录下所有.md文件。确认执行?(y/n): ")
if confirm.lower() in ['y', 'yes', '是']:
process_markdown_files()
else:
print("操作已取消")
gitalk评论的启用
gitalk是一个借助github issue开发评论的组件。它会在指定的仓库内发布issue,通过issue的标签Labels来对每一篇博客进行关联,一个issue都对应一篇博客的评论区。优点是方便简单,只需要一个github账号便可启用。
首先需要新建一个OAuth Apps,在"Homepage URL"和"Authorization callback URL"都填入博客的地址。建立完毕后就可以获得clientID,然后生成一个clientSecret。
然后新建一个仓库用于issue的存放,之后所有的评论都会放在该仓库的issue中。
回到hugo.toml中进行编辑:
[params]
enableGitalk = false
[params.gitalk]
clientID = "刚刚获取的clientID"
clientSecret = "刚刚获取的clientSecret"
repo = "刚刚新建的仓库名" # 不需要带用户名,只要仓库名称即可。
owner = "your github" # 您的GitHub ID
admin = "your github" # 您的GitHub ID
id = "location.pathname" # 文章页面的链接地址就是ID
labels = "gitalk" # Github issue labels.
perPage = 100 # 可评论数量,最大为100
pagerDirection = "last" # 时间倒序来排列评论
createIssueManually = true # 设置为true,如果是管理员登录,会自动创建issue,如果是false,需要管理员手动添加第一个评论(issue)
distractionFreeMode = false # 如果启用,则输入 (cmd|ctrl + enter) 可以快速回复
# proxy = "https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token" # 为不方便登录github的读者通过反向代理,可自行搭建
而这个时候,熟悉的大坑时间又来了,算算这是第四个大坑了,大坑好多。我的gitalk显示Error: Validation Failed报错,也就是未找到可关联的issue。
造成这个报错是由好几个因素叠加造成的:
- gitalk在关联issue和文章的时候,会自动生成两个Labels并打上相关的issue,一个是上面设置的
labels = "gitalk",一个是直接将博客文章的网址放入到Labels中。 - Labels命名的限制是50个字符。
- hugo生成博客文章网址的时候,是根据文章所在的markdown文件名称来生成的。
由于这三个因素叠加,文章所在的markdown文件名称所在的文件名称是中文时,实际的生成的网址会非常长,很容易就突破50个字符的大关。这边是这次报错的由来。
GitHub的Labels有50个字符的长度限制,是固定死的。我们所能做的就只能是缩短字符数量。这里就有两个缩短字符的思路:
- 将markdown文件改为较为简短的英文,
- 寻找其他字符串作为关联不同文章的Labels名称,这个字符串是需要唯一的。
前者好说,改文件名就行,但这种方式不方便obsidian笔记和博客文章的相互对照。因此我选择第二个方式,而字符串的方式选择md5哈希
md5哈希作为gitalk绕过labels限制的办法其实很早就有,这里就直接给出成品。需要编辑themes/github-style/layouts/partials/gitalk.html
{{ if .Site.Params.enableGitalk }}
{{/* 检查是否启用了Gitalk评论系统 */}}
<div id="gitalk-container" class="gitalk-container"></div>
{{/* Gitalk评论容器,评论将渲染在这个div中 */}}
<!-- 引入MD5库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
{{/* 引入MD5加密库,用于生成固定长度的页面ID */}}
<link rel="stylesheet" href='{{ "css/gitalk.css" | absURL }}'>
{{/* 引入Gitalk样式文件 */}}
<script src='{{ "js/gitalk.min.js" | absURL }}'></script>
{{/* 引入Gitalk主脚本文件 */}}
<script>
// 获取当前页面的路径(不包含域名)
var pageUrl = window.location.pathname;
// 对页面路径进行MD5哈希,生成固定长度的唯一ID
// 解决GitHub标签50字符长度限制的问题
var hashId = md5(pageUrl);
// 初始化Gitalk评论系统
const gitalk = new Gitalk({
clientID: '{{ .Site.Params.gitalk.clientID }}', // GitHub OAuth应用的Client ID
clientSecret: '{{ .Site.Params.gitalk.clientSecret }}', // GitHub OAuth应用的Client Secret
repo: '{{ .Site.Params.gitalk.repo }}', // 存放评论的GitHub仓库名
owner: '{{ .Site.Params.gitalk.owner }}', // GitHub仓库所有者用户名
admin: ['{{ .Site.Params.gitalk.owner }}'], // 管理员用户名列表(可自动创建Issue)
id: hashId, // 页面唯一标识符(使用MD5哈希值)
title: '{{ .Title }}' || document.title, // Issue标题,使用文章标题或页面标题
distractionFreeMode: false // 禁用无干扰模式(允许使用快捷键提交)
});
// 立即执行函数,用于渲染Gitalk评论框
(function() {
// 检查是否在本地开发环境(localhost或127.0.0.1)
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
// 本地环境不渲染Gitalk,显示提示信息
document.getElementById('gitalk-container').innerHTML = 'Gitalk评论在本地预览时默认不显示。';
return;
}
// 在生产环境中渲染Gitalk评论框
gitalk.render('gitalk-container');
})();
</script>
{{ end }}
{{/* 结束Gitalk启用条件判断 */}}
还需要编辑hugo.tom,将id = "location.pathname"注释掉
# id = "location.pathname"
现在重新部署,gitalk应该就恢复正常了。
代码块高亮
第五个坑,便是代码高亮。由于代码块背景一直是黑色的,跟github那种浅灰的风格非常不搭配,索性重新加载了一个css文件来更改代码块的背景颜色。
需要到themes/github-style/static/css中新建一个custom.css,并写入代码块背景的样式
/* 专门针对代码块的背景色 */
pre, code, .highlight, .chroma {
background-color: var(--color-canvas-subtle, #f6f8fa) !important;
/* 使用CSS变量,如果变量不存在则回退到#f6f8fa */
}
/* 确保代码块文字颜色也是深色 */
pre, code {
color: var(--color-fg-default, #24292f) !important;
}
回到hugo.toml,设置加载custom.css
pygmentsUseClasses = true
[params]
custom_css = ["css/custom.css"]
重新部署后代码块应该就会变成github的浅灰色风格了。
尾声
在吃下这么多坑之后,也是终于将博客调教成自己想要的模样了,真是一场酣畅淋漓的大战啊。