一篇从零开始的 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")
}
```

![说明文字](/images/demo.png)

几点说明:

  • 草稿机制: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. 绑定自定义域名(可选) #

  1. 仓库 Settings → Pages → Custom domain 填域名,勾选 Enforce HTTPS
  2. DNS 加一条 CNAME 记录:blog → <用户名>.github.io
  3. 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

升级前的三个习惯 #

  1. 先读 CHANGELOG。破坏性改动(比如某版本"主题不再内置 logo,改由站点配置驱动”)通常会加粗标注,并给出迁移方法。不读直接升,可能站点就少个 logo。
  2. 检查配置兼容性。看到"新增 params.xxx“就去补配置,看到"移除"就去清理。
  3. 本地 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 样式全 404baseURL 和实际访问地址(含仓库名)不一致
Actions 报找不到主题checkout 少了 submodules: recursive

十二、小结 #

整个流程其实就四步:

  1. 装 Hugo → hugo version 能跑
  2. 建站 + 装主题 → hugo new site,submodule 引入 pixel-blog-hugo
  3. 写 Markdown → hugo new posts/xxx.md,改 draft: false
  4. 上线 → GitHub Pages 免费托管,或自建服务器 + Actions 自动发布

往后的日常就只剩一件事:写文章,push,等它上线。

主题完全开源(MIT),觉得还行就点个 Star;有问题、有想法欢迎提 Issue: