跳过正文

从零搭建 Hugo 博客的完整记录

·2892 字·6 分钟· loading · loading ·
JiangNight
作者
JiangNight
目录

这篇将介绍当前博客从 0 折腾到上线的完整记录

先说最终目标
#

结果很具体:

  • Hugo 本地能正常开发
  • Blowfish 主题 + 中文界面
  • 文章页有封面、目录、标签
  • 接入 Giscus 评论
  • 接入 Firebase 阅读量/点赞
  • 推到 Vercel 自动部署

如果你的目标和我差不多,这篇基本可以直接复刻

1) 为什么最后选 Hugo
#

我一开始在 Hexo、Jekyll、Next.js 之间来回看,最后选 Hugo 的理由就两条:

  1. :本地构建和热更新都很快
  2. :不依赖 Node.js,装一个二进制就能跑

因为我的开发环境是在MacOS,所以当前文章Mac可以1:1跟着操作,其他系统可以自行尝试

brew install hugo
hugo version
git --version

hugo version 最好确认一下是 extended 版本(很多主题会用到它)。

2) 初始化站点 + 安装 Blowfish
#

先创建一个空站点:

hugo new site blog
cd blog

接着装 Blowfish。这里我用 git submodule,原因是后续更新主题比较方便,也能清楚知道“主题代码”和“自己代码”的边界。

git init
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish

我第一次就卡在这里:以为写了 theme = "blowfish" 就能显示,结果页面空白。后来才发现主题目录根本没拉下来 (

装完建议先看一眼目录:

ls themes/blowfish

能看到主题文件再继续,不然后面改配置都是白改。

3) 把主题默认配置复制到项目里
#

Blowfish 不是只改一个 hugo.toml 就完事。它的大部分配置都在 config/_default/

这一步必须做:

cp themes/blowfish/config/_default/*.toml config/_default/

复制后你会看到这些文件(后面会频繁改):

  • config/_default/hugo.toml
  • config/_default/languages.en.toml
  • config/_default/menus.en.toml
  • config/_default/markup.toml
  • config/_default/params.toml

4) 中文化(这一步最容易漏)
#

Blowfish 的多语言是靠文件名后缀识别的,所以不能只改英文文件内容,而是要复制出中文文件。

cp config/_default/languages.en.toml config/_default/languages.zh-cn.toml
cp config/_default/menus.en.toml config/_default/menus.zh-cn.toml

然后改 config/_default/hugo.toml

theme = "blowfish"
defaultContentLanguage = "zh-cn"
hasCJKLanguage = true

hasCJKLanguage = true 这行不要省。中文没有空格,Hugo 默认按英文分词算阅读时间,不开这个会算得很离谱。

4.1 改语言文件
#

config/_default/languages.zh-cn.toml 我主要改这些:

languageCode = "zh-cn"
languageName = "中文"
title = "你的博客名"

[params]
  displayName = "ZH"
  isoCode = "zh-cn"
  dateFormat = "2006年1月2日"

这里的 2006年1月2日 不是示例日期写着玩的,它是 Go 语言固定的时间模板语法。

4.2 改菜单文件
#

config/_default/menus.zh-cn.toml

[[main]]
  name = "文章"
  pageRef = "posts"
  weight = 10

[[main]]
  name = "分类"
  pageRef = "categories"
  weight = 20

[[main]]
  name = "标签"
  pageRef = "tags"
  weight = 30

如果你后面发现导航还是英文,优先检查两件事:

  1. defaultContentLanguage 是否真的是 zh-cn
  2. 是否真的有 menus.zh-cn.toml(文件名后缀拼错很常见)

5) 把列表页标题也改成中文
#

只改菜单不够,列表页标题默认还会显示 Posts

做法是给每个列表目录加 _index.md

mkdir -p content/posts content/categories content/tags

content/posts/_index.md

---
title: "文章"
---

content/categories/_index.mdcontent/tags/_index.md 同理,标题分别写“分类”“标签”。

6) 首页、文章页、列表页参数(都在 params.toml
#

我这部分花的时间最多。建议先别一次改太多,每改一段就开本地服务看效果。

hugo server -D

6.1 首页
#

config/_default/params.toml

[homepage]
  layout = "background"
  homepageImage = "img/background.svg"
  showRecent = true
  showRecentItems = 5
  showMoreLink = true
  cardView = true
  layoutBackgroundBlur = true

layout 常见有 page/profile/hero/card/background

我最开始选了 profile,然后纳闷为什么背景图怎么都不出来,后来才知道这个布局本来就不吃 homepageImage

6.2 文章页
#

[article]
  showHero = true
  heroStyle = "background"
  showTableOfContents = true
  showTaxonomies = true
  showReadingTime = true
  showComments = true

如果 heroStyle = "background",建议每篇文章都放封面图。

Blowfish 推荐用 leaf bundle 结构:

content/posts/2026/02/09/my-post/
├── index.md
└── featured.jpg

封面文件名用 featured.* 最省心,主题会自动识别。

6.3 列表、分类、标签页
#

[list]
  showHero = true
  heroStyle = "background"
  showSummary = true
  showCards = true
  groupByYear = false
  cardView = true

[taxonomy]
  showHero = true
  heroStyle = "background"

[term]
  showHero = true
  heroStyle = "background"

这几段不配的话,分类/标签页会显得特别“裸”。

7) 写第一篇文章(顺手验证目录结构)
#

我习惯直接用 Hugo 命令创建文章:

hugo new content posts/2026/02/09/hello-world/index.md

然后把封面图放到同目录,比如:

content/posts/2026/02/09/hello-world/
├── index.md
└── featured.jpg

这一步其实在验证两件事:

  1. 路径组织是否符合自己的长期习惯(按日期、按专题都行)
  2. 主题对封面的自动识别是否正常

8) 评论系统:Giscus
#

我选 Giscus 的原因很现实:免费、干净、基于 GitHub Discussions,不用自己维护后端。

8.1 前置条件
#

  1. 准备一个 公开 GitHub 仓库
  2. 开启 Discussions
  3. 安装 giscus app
  4. giscus 配置页 生成脚本参数

8.2 放到主题 partial 里
#

创建(或修改)layouts/partials/comments.html

<script src="https://giscus.app/client.js"
  data-repo="你的用户名/仓库名"
  data-repo-id="你的repo-id"
  data-category="Announcements"
  data-category-id="你的category-id"
  data-mapping="pathname"
  data-strict="0"
  data-reactions-enabled="1"
  data-emit-metadata="0"
  data-input-position="bottom"
  data-theme="preferred_color_scheme"
  data-lang="zh-CN"
  crossorigin="anonymous"
  async>
</script>

8.3 让评论区跟随日/夜模式切换
#

默认 preferred_color_scheme 只能跟系统主题,用户手动切换站点深浅色时,Giscus iframe 往往不会同步。

我最后加了下面这段:

<script>
  function setGiscusTheme(theme) {
    const iframe = document.querySelector('iframe.giscus-frame');
    if (!iframe) return;
    iframe.contentWindow.postMessage(
      { giscus: { setConfig: { theme: theme } } },
      'https://giscus.app'
    );
  }

  const observer = new MutationObserver(() => {
    const isDark = document.documentElement.classList.contains('dark');
    setGiscusTheme(isDark ? 'dark' : 'light');
  });

  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['class']
  });

  window.addEventListener('message', (event) => {
    if (event.origin !== 'https://giscus.app') return;
    const isDark = document.documentElement.classList.contains('dark');
    setGiscusTheme(isDark ? 'dark' : 'light');
  });
</script>

原理很朴素:监听 <html>class,一旦出现/移除 dark,就 postMessage 通知 Giscus 改主题。

9) 阅读计数与点赞:Firebase
#

Blowfish 已经内置了 Firestore 的阅读计数逻辑,接入不复杂,但步骤比较碎。

9.1 Firebase 控制台里要做的事
#

  1. 创建项目
  2. 开启 Firestore Database
  3. 开启 Authentication 的 Anonymous 登录
  4. 注册一个 Web App,拿到配置参数

9.2 回填 params.toml
#

[firebase]
  apiKey = "你的apiKey"
  authDomain = "你的项目.firebaseapp.com"
  projectId = "你的项目id"
  storageBucket = "你的项目.firebasestorage.app"
  messagingSenderId = "你的id"
  appId = "你的appId"
  measurementId = "你的measurementId"

[article]
  showViews = true
  showLikes = true

9.3 Firestore 安全规则
#

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }
  }
}

这里我踩了一个小坑:改完配置后页面没反应。最后发现是 hugo server 热更新没有完全重载 Firebase 相关脚本,重启一次就好了。

10) 部署到 Vercel
#

本地跑通以后,再上部署就轻松很多。

10.1 .gitignore
#

public/
resources/
.hugo_build.lock
.DS_Store

10.2 vercel.json
#

Vercel 默认 Hugo 版本可能比较旧,我这里显式指定了版本:

{
  "build": {
    "env": {
      "HUGO_VERSION": "0.155.2"
    }
  }
}

导入仓库时参数:

  • Framework Preset:Hugo
  • Build Command:hugo --minify
  • Output Directory:public

10.3 绑定自定义域名
#

部署成功后,在 Vercel 的 Settings -> Domains 里加域名,然后去域名服务商配置 CNAME:

类型名称
CNAMEblogcname.vercel-dns.com

DNS 生效后,Vercel 会自动发证书。

11) 我最终的目录结构
#

blog/
├── config/_default/
│   ├── hugo.toml
│   ├── languages.zh-cn.toml
│   ├── menus.zh-cn.toml
│   ├── markup.toml
│   └── params.toml
├── content/
│   ├── posts/
│   │   ├── _index.md
│   │   └── 2026/02/09/hello-world/
│   │       ├── index.md
│   │       └── featured.jpg
│   ├── categories/_index.md
│   └── tags/_index.md
├── layouts/partials/
│   └── comments.html
├── assets/img/
├── themes/blowfish/
├── vercel.json
└── .gitignore

我踩过的坑(按出现顺序)
#

  • 写了主题名但页面空白:主题目录没真正下载到 themes/
  • 首页背景图不显示:layout 选成了 profile
  • 菜单还是英文:menus.zh-cn.toml 缺失或命名不对
  • 阅读时间离谱:忘记 hasCJKLanguage = true
  • Firebase 没生效:改完配置没重启 hugo server
  • Vercel 构建挂掉:Hugo 版本太老,没在 vercel.json 指定

收尾
#

这次搭建给我的最大感受是:Hugo 本身不难,真正费时间的是“主题约定 + 第三方服务接入 + 细节排错”。

如果你也正准备搭个人博客,建议按这个顺序做:

  1. 先把本地页面跑起来
  2. 再做中文化和页面样式
  3. 最后再接评论、统计和部署

这样每一步都能看到明确反馈,出问题也更容易定位。