一篇从零开始的 Hugo 博客搭建教程。以安装我自己开发的像素风主题 pixel-blog-hugo 为例,从下载 Hugo 一路讲到文章上线——既能丢到 GitHub Pages,也能发布到自己的服务器。最后附上升级主题的正确姿势。
一、为什么用 Hugo #
Hugo 是一个静态网站生成器。你负责写 Markdown,它负责把 Markdown 一键编译成纯 HTML/CSS/JS 文件。
好处很实在:
- 服务器零运行时:产物就是一堆静态文件,nginx 只负责"递文件",没有数据库、没有后台,2G 小机器毫无压力
- 构建快:几百篇文章通常一秒内编译完
- 天然安全:没有可被攻击的后台
- 数据可迁移:你的博客就是一堆 Markdown 文件,用 git 管理,永不丢失
一句话概括工作流:
本地写 Markdown → hugo 生成 public/ → 把 public/ 传出去
“传出去"这一步,可以传到自己服务器,也可以交给 GitHub Pages。两条路后面都会讲。
二、安装 Hugo #
Windows #
推荐用包管理器 Scoop 或 Chocolatey :
# Scoop
scoop install hugo-extended
# Chocolatey
choco install hugo-extended
也可以直接去 Hugo Releases
下载对应系统的压缩包,解压后把 hugo.exe 放进 PATH。
macOS #
brew install hugo
Linux(Debian / Ubuntu) #
sudo apt install -y hugo
# 或者用 snap
sudo snap install hugo
装完验证:
hugo version
能打印出版本号即可,例如:
hugo v0.166.0-...+extended linux/amd64
建议装 extended 版本(版本号里带
extended字样)。个别主题会用到 Sass 编译,普通版可能报错。本文使用的 pixel-blog-hugo 要求 Hugo ≥ 0.158,extended 非必需,但装上更省心。
三、创建站点 #
hugo new site my-blog
cd my-blog
git init
生成的标准骨架长这样:
my-blog/
├── archetypes/ # 新建文章时套用的模板
├── assets/ # 需要 Hugo 处理的资源(图片管线、Sass)
├── content/ # ★ 文章、页面都放这里(Markdown)
├── static/ # 原样复制到产物的文件(图标、robots 等)
├── themes/ # 主题目录
├── layouts/ # 自定义模板(覆写主题用,一般用不到)
├── hugo.toml # ★ 站点配置文件(核心)
└── resources/ # Hugo 的编译缓存(不用管)
assets/ 和 static/ 的区别值得先记住:
assets/:会被 Hugo"加工"的地方。可以裁剪图片、压缩、加指纹。主题里的 logo 通常走这里。static/:原样复制。里面的文件按路径直接可访问,配置里写/xxx.png就是引用它。
一句话判断:模板/代码要"读取再处理"的放 assets/,配置里只填最终 URL 路径的放 static/。
四、安装主题 #
这里以安装我写的 pixel-blog-hugo 为例。它是个像素复古风的主题,零圆角、2px 硬边框,明暗双主题。
安装方式三选一:
方式 A:Git submodule(推荐) #
git submodule add https://github.com/LittleWin8/pixel-blog-hugo themes/pixel-blog-hugo
用 submodule 的好处是:主题版本被你的博客仓库"锁定"在某个提交上,升级、回滚都很干净(后面专门讲怎么升级)。
方式 B:Hugo Modules #
适合已经装了 Go 的人:
hugo mod init github.com/<你的用户名>/my-blog
hugo mod get github.com/LittleWin8/pixel-blog-hugo
然后在 hugo.toml 里删掉 theme = ...,改成:
[module]
[[module.imports]]
path = "github.com/LittleWin8/pixel-blog-hugo"
方式 A 需要在
hugo.toml里写theme = "pixel-blog-hugo";方式 B 用 module 引入,不写theme。
五、配置 hugo.toml #
主题自带一份完整示例配置:exampleSite/hugo.toml
。最省事的做法是把它整个粘进你站点的 hugo.toml,再按注释改成你自己的信息。
先看一段最小可用的核心配置:
baseURL = "https://example.com/"
locale = "zh-cn"
defaultContentLanguage = "zh"
title = "我的博客"
theme = "pixel-blog-hugo"
enableRobotsTXT = true
hasCJKLanguage = true # 中文站点建议开启(阅读时长/摘要更准)
summaryLength = 80
mainSections = ["posts"] # 主内容区块:首页最新文章、归档、相关文章、搜索都基于它
[outputs]
home = ["HTML", "RSS", "JSON"] # JSON 供站内搜索使用
[pagination]
pagerSize = 8
[taxonomies]
tag = "tags"
[markup]
[markup.goldmark.parser]
wrapStandAloneImageWithinParagraph = false
[markup.goldmark.renderer]
unsafe = true
[markup.highlight]
noClasses = false
[markup.tableofcontents]
startLevel = 2
endLevel = 4
[params] 下的个性化项很多(备案号、社交链接、技术栈、友链、导航开关……),不用全背,照 示例配置的注释
按需填即可。这里只列最常用的三个:
[params]
author = "你的名字"
[params.identity] # 顶栏名称与 logo
name = "你的名字"
logo = "images/logo.png" # 放你站点的 assets/images/ 下
favicon = "images/logo.png"
[params.hero] # 首页主视觉文案
title = "你的名字"
tagline = "一句话签名"
intro = "自我介绍,支持 Markdown。"
想配备案号、社交图标、友链、技术栈等,直接翻主题 README 的「个性化配置」和示例配置注释,写得很全,这里不赘述。
⚠️ 记住一条原则:改自己的博客,只动你自己站点里的
hugo.toml,不用碰主题文件。baseURL、title、theme、[outputs]、[markup]这类站点级配置也只能写在站点根目录,写在主题里无效。
六、写第一篇文章 #
hugo new posts/hello.md
会生成 content/posts/hello.md。开头是 front matter(+++ 包裹的元信息),下面是正文:
+++
title = "第一篇:博客开张"
date = 2026-09-29
draft = false # ← 改成 false 才会正式发布
tags = ["随笔"]
+++
正文直接用 Markdown 写,代码块会自动高亮,插图放 static/images/ 后用 /images/xxx.png 引用:
## 小标题
```go
func main() {
fmt.Println("hello")
}
```

几点说明:
- 草稿机制:
draft: true只在本地预览(加-D)时可见,构建时会被跳过。发布前记得改false。 - 图片管理:放
static/images/,构建时自动拷进产物。小成本、不走图床,零折腾。 - 目录:主题会自动为文章生成右侧粘性目录(大纲),不用手写。层级由站点配置
[markup.tableofcontents]决定(本文示例为 h2–h4,Hugo 默认 h2–h3);窄屏下目录会移回正文上方。
写一个项目(作品集) #
标准博客主题通常只有文章、标签、归档,不带「项目」这种板块——主题的 /projects/ 是给作品集准备的:如果你只写文章,这节可以直接跳过;如果你有个人项目、开源作品想在博客里单独展示成一个板块,再往下看。
每个项目一个 Markdown:
hugo new projects/my-app.md
hugo new 生成的骨架里带全部字段的注释,最常用的几个是:
---
title: "Pixel Notes"
online: true # 是否上线(决定绿点与「在线体验」按钮)
link: "https://example.com" # 「在线体验」按钮
source: "https://github.com/you/repo" # 「查看源码」按钮
icon: "📝" # 卡片图标(emoji 或文字)
tags: ["Vue.js", "TypeScript"]
summary: "一句话介绍。"
---
完整字段(
status状态徽章、featured重点项目、卡片配图、排序等)见主题 README 的「项目」一节 ,骨架注释里也都有。
其它页面(按需) #
归档、搜索、友链、关于这几个页面,都是「一个内容文件 + 主题布局」,在站点里各建一个文件即可:
---
title: "归档"
layout: "archives"
---
依次还有 layout: "search" 的 content/search.md、layout: "links" 的 content/links.md,关于页直接 hugo new about.md。用不到的直接删掉文件、并从导航里移除入口即可。具体字段和导航配置见主题 README。
七、本地预览与构建 #
hugo server -D
浏览器打开 http://localhost:1313 实时预览(-D 包含草稿),改文件会自动刷新。Ctrl+C 停止。
确认没问题后,正式构建:
hugo --minify
产物在 public/ 目录,这就是要部署的全部内容。记得在 .gitignore 里加上:
public/
resources/
产物和缓存不进仓库,源码才是源头。
八、部署方案一:GitHub Pages #
没有服务器、想纯零成本试水,用 GitHub Pages 最合适。它是免费的,自带 HTTPS。
1. 把博客源码推到 GitHub #
cd my-blog
git add -A && git commit -m "init blog"
git remote add origin https://github.com/<你的用户名>/<仓库名>.git
git push -u origin main
2. 添加构建工作流 #
新建 .github/workflows/pages.yml:
name: pages
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
submodules: recursive # 用 submodule 装主题时必须有
- name: 安装 Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.166.0'
extended: true
- name: 构建
run: hugo --minify
- uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- id: deployment
uses: actions/deploy-pages@v4
用 submodule(方式 A)装的主题,
actions/checkout一定要带submodules: recursive,否则云端拉不到主题,构建会失败。
3. 开启 Pages #
仓库 Settings → Pages → Source 选 GitHub Actions,然后 push 或手动触发一次工作流,跑完即上线。
4. ⚠️ baseURL 是最大的坑 #
默认地址是 https://<用户名>.github.io/<仓库名>/,带仓库名路径。hugo.toml 里的 baseURL 必须和它完全一致,否则样式、图片会全部 404:
- 用默认地址 →
baseURL = "https://littlewin8.github.io/blog/" - 绑自定义域名 →
baseURL = "https://blog.example.com/"
5. 绑定自定义域名(可选) #
- 仓库 Settings → Pages → Custom domain 填域名,勾选 Enforce HTTPS
- DNS 加一条 CNAME 记录:
blog→<用户名>.github.io baseURL改成自定义域名版本,再 push
九、部署方案二:自己的服务器 #
如果你有服务器(比如阿里云 ECS),静态博客的部署可以简单到三条命令。
前提:服务器已有 nginx,并且配置了一个站点指向博客目录,比如 /opt/blog/。
# 1. 本地打包产物
tar -czf /tmp/blog-dist.tar.gz -C public .
# 2. 上传到服务器
scp /tmp/blog-dist.tar.gz root@<服务器IP>:/tmp/
# 3. 服务器上解压覆盖
ssh root@<服务器IP> "rm -rf /opt/blog/* && tar -xzf /tmp/blog-dist.tar.gz -C /opt/blog && rm /tmp/blog-dist.tar.gz"
说明:
- 先清掉旧文件再解压,避免残留
- nginx 指向该目录后,发布完不用重启任何服务,静态文件即时生效
嫌麻烦可以存成脚本 publish-blog.sh,以后一键发布:
#!/usr/bin/env bash
set -e
hugo -s ~/my-blog --minify &&
tar -czf /tmp/blog-dist.tar.gz -C ~/my-blog/public . &&
scp /tmp/blog-dist.tar.gz root@<服务器IP>:/tmp/ &&
ssh root@<服务器IP> "rm -rf /opt/blog/* && tar -xzf /tmp/blog-dist.tar.gz -C /opt/blog && rm /tmp/blog-dist.tar.gz" &&
echo "博客已发布"
进阶:用 GitHub Actions 自动部署 #
懒得每次本地构建?让 GitHub 帮你构建、发布。新建 .github/workflows/deploy.yml:
name: deploy-blog
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: 安装 Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.166.0'
extended: true
- name: 构建
run: hugo --minify
- name: 配置 SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SERVER_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ secrets.SERVER_HOST }}" >> ~/.ssh/known_hosts
- name: 发布
run: |
tar -czf /tmp/blog-dist.tar.gz -C public .
scp -i ~/.ssh/deploy_key /tmp/blog-dist.tar.gz root@${{ secrets.SERVER_HOST }}:/tmp/
ssh -i ~/.ssh/deploy_key root@${{ secrets.SERVER_HOST }} "rm -rf /opt/blog/* && tar -xzf /tmp/blog-dist.tar.gz -C /opt/blog && rm -f /tmp/blog-dist.tar.gz"
在仓库 Settings → Secrets and variables → Actions 里加两个 Secret:
SERVER_SSH_KEY:服务器私钥(用ssh-keygen生成,公钥追加到服务器~/.ssh/authorized_keys)SERVER_HOST:服务器 IP 或域名
之后只管 push,自动上线。
小区别:GitHub Pages 用的是
id-token/ Pages 权限;自建服务器用的是 SSH Secret。两者不冲突,也可以一个仓库同时配。
十、上游主题更新了,怎么同步 #
主题是持续迭代的(CHANGELOG 会记录每个版本的变化)。升级方式取决于你当初怎么装的主题。
用 submodule 装的 #
# 拉到主题最新提交
git submodule update --remote themes/pixel-blog-hugo
# 提交指针更新(submodule 更新后必须提交,否则只是本地生效)
git add themes/pixel-blog-hugo
git commit -m "chore: 更新主题"
想升级到指定的稳定版本(推荐,可控),先看 Releases 里的 tag,再:
cd themes/pixel-blog-hugo
git fetch origin --tags
git checkout v0.4.1 # 换成你要的版本号
cd ../..
git add themes/pixel-blog-hugo
git commit -m "chore: 更新主题至 v0.4.1"
用 Hugo Modules 装的 #
hugo mod get -u github.com/LittleWin8/pixel-blog-hugo # 升到最新
# 或指定版本
hugo mod get github.com/LittleWin8/pixel-blog-hugo@v0.4.1
升级前的三个习惯 #
- 先读 CHANGELOG。破坏性改动(比如某版本"主题不再内置 logo,改由站点配置驱动”)通常会加粗标注,并给出迁移方法。不读直接升,可能站点就少个 logo。
- 检查配置兼容性。看到"新增
params.xxx“就去补配置,看到"移除"就去清理。 - 本地
hugo server验证后再提交。升级后先本地跑一遍,确认没报错、样式正常,再提交推送触发部署。
另外,如果你曾经为了改点东西直接动过主题里的文件,升级会有覆盖风险。正确做法是”项目级覆盖":在你站点根目录建同名路径的文件(如 layouts/_partials/post-meta.html),Hugo 会优先读取你站点的版本,主题更新也不会冲掉你的改动。
十一、常见问题 #
| 问题 | 排查方向 |
|---|---|
| 页面样式全丢(裸 HTML) | theme = "..." 名字拼错,或主题没装到 themes/<主题名> |
| 文章列表是空的 | front matter 里 draft: true 没改;本地预览要加 -D |
| 构建报 theme not found | 目录名要大小写一致,themes/pixel-blog-hugo |
| 中文标签页 404 | 通常是 URL 编码,从导航里的"标签"入口进即可 |
| 图片不显示 | 放错目录:static/ 下用 /images/x.png 引用;assets/ 下的写资源路径 |
| 改了配置不生效 | 重新 hugo 构建 + 重新部署。产物是静态的,不会热更新 |
| GitHub Pages 样式全 404 | baseURL 和实际访问地址(含仓库名)不一致 |
| Actions 报找不到主题 | checkout 少了 submodules: recursive |
十二、小结 #
整个流程其实就四步:
- 装 Hugo →
hugo version能跑 - 建站 + 装主题 →
hugo new site,submodule 引入 pixel-blog-hugo - 写 Markdown →
hugo new posts/xxx.md,改draft: false - 上线 → GitHub Pages 免费托管,或自建服务器 + Actions 自动发布
往后的日常就只剩一件事:写文章,push,等它上线。
主题完全开源(MIT),觉得还行就点个 Star;有问题、有想法欢迎提 Issue:
- 主题仓库:pixel-blog-hugo
- 示例站与配置:exampleSite/hugo.toml