主題維護者對 Hugo 的心得雜談

從使用者變成主題維護者,對 Hugo 越來越熟悉,也慢慢了解 Hugo 的設計理念和優勢,所以決定寫一下我的理解,畢竟網路上沒文章在說這些東西,裡面會有很多開發者相關的內容。

Hugo 的優點

重要的話放前面講。

我起初也是對 Hugo 的純 HTML 模板很反感,要找原始碼很麻煩,幾乎沒有上下文,只能透過 IDE 全局搜尋字串找內容,但是現在逐漸理解他的優勢,Hugo 最大的優點就是「完全無須理解前端複雜生態」,不管前端有什麼 npm yarn pnpm bun,什麼 React Vue Angular Next.js 各種框架,SSR CSR Hydration 一堆有的沒的問題全都與我們無關,基本上 Hugo 就只會有 HTML/CSS/JS 三個打天下,不用學一堆有的沒的奇怪知識,我們沒有要成為前端工程師

Hugo 沒有任何依賴,只要一個二進制包就可以在任何地方運行,這也代表不管過 10 年 20 年永遠都可以用同一個二進制檔案編譯你的 Markdown 文件不需要任何語法更新,沒有套件失效找不到的問題,也沒有任何麻煩的依賴管理,20 年後唯一要更新的就是舊版 CSS 語法瀏覽器可能不支援了,但是 Hugo 的二進制包還是活的好好的。

Hugo 很快,甚至和 Zola 有得比。中少量文件兩者伯仲之間,而海量文件 Zola 雖然會比 Hugo 快一個數量級,但是有海量文件的情況就不可能全部都編譯,只會進行增量編譯,Hugo 對此雖然沒有完美支援但是也算是有高度支援,例如 ignoreFiles segments 允許部分編譯,而 Zola 只能靠 Rust 的效能硬扛,對於其他 SSG 更不用說那是無情碾壓,他們壓根就沒有考慮這種海量文件的場景,可以看 Hugo 開發者說他們甚至可以把美國政府的歷史紙本文件數位化

Hugo 很容易客製化主題,內建的 override 機制可以任意客製化任何檔案,沒有任何檔案不能客製化的,對比 Docusaurus 高度支援但還是有限的客製化支援,以及 Vitepress 上手難度相對高的客製化,指定檔案的客製化輕鬆很多。hexo 雖然可以直接修改主題原始碼,但是沒有 override 功能,主題更新就需要手動合併上游更新。

Hugo 發展已經足夠久,支援各種想的到想不到的方法,例如 slug frontmatter 在 Vitepress 就不支援,還有 permalink 各種設定、相關文章、上下一篇文章等等;可以直接在 Markdown 裡面隨便寫 HTML,不用像 Docusaurus 寫那種彆腳的 JSX 語法,shortcode 簡單易懂功能豐富自定義輕鬆,現在也整合 TS/SASS/Tailwind;主題爆多,絕對碾壓其他所有靜態網站生成器。

Hugo 由於其強烈基於 HTML 的原因,絕大多數主題即使沒有 JS 的環境大部分功能也能正常運作,而其他基於前端思維 SSG 做出來的網頁更依賴瀏覽器 JS 環境

Hugo 的缺點

Hugo 的 Go 模板需要額外時間理解,Go 模板混用 HTML IDE 支援困難,需要額外理解 Hugo/Go 語法和渲染方式,不支援所有 Go 語法,文檔雖然非常詳細,但是編排對用戶和開發者都雜亂難理解。

Hugo 要使用現代前端技術非常困難,因為基本上只有 HTML/CSS/JS 三件套,要搞個 SPA 也很難,根本沒有幾個主題支援 SPA,沒有框架所有東西只能手搓,這代表所有 Hugo 主題都不會太複雜。

Hugo 可以整合現代前端開發但是有點麻煩,因為他和 npm 就是不同系統的東西。

Hugo 可以當作沒有函式這種東西,唯一最像函式的 partial 限制一堆,連輸入輸出都沒辦法明確標示,開發很痛苦。

Hugo 十年了還不是穩定版本,三不五時還是會搞一些影響下游的更新。

Hugo 沒有金主爸爸支援,像是 Next.js 有 Vercel,Docusaurus 有 Facebook,Hugo 是純開源的專案,看天吃飯。

專案結構

初學者專用,有需要就看文檔解釋專案結構

該用哪種方式安裝主題

可能是因為我不是 Go 開發者,說實在我看不出使用 Hugo module 安裝主題有什麼好處,可以看這兩篇討論,bep 和 jmooring 兩個主要維護者都有回覆:

第二篇的 Git 缺點說錯了,Git submodule 也可以自由控制版本,於是這樣說來 Hugo module 根本沒優勢,使用 submodule 還可以隨意檢視專案原始碼而不用開新專案獨立檢視,況且用過 Git 的人絕對大於用過 Go 的人。

如何修改主題

Hugo 會自動在你的專案根目錄找檔案,如果沒有就會套用主題目錄的檔案,所以千萬不要直接修改 themes/YOUR_THEME 的內容,而是複製主題目錄的 layouts/CSS 或者其他你要改的檔案,貼上到自己專案根目錄的相對位置再進行修改。

圖片到底要放在哪

完全不會經過 Hugo 管道處理的放在 static,到處都會用到的放在 assets,文章目錄相關的放在 Markdown 同資料夾中。

static 的內容會原封不動的複製到專案輸出位置完全沒有額外處理,assets 和 content 目錄中的圖片則是可以被 Hugo 管道處理,如果沒有處理到的就是主題開發者寫錯,可以用 resources.Get.Resources.Get 檢查主題原始碼看看有沒有正確處理全局(包含 assets)和頁面(同一 page bundle 底下)的圖片。

不過我建議放在 CDN 上,這讓你的 Markdown 便攜和遷移能力又往上一個層級,方式請見架設Cloudflare R2免費圖床,給Hugo靜態網站託管圖片,他使用 rclone 作為和 CDN 的交互介面,或者可以參考不看后悔!CloudFlare R2 图床搭建教程,可能是目前最完美的解决方案!以 PicGo 作為介面,我個人則是使用 PicList,這是用法和 PicGo 幾乎相同但是使用體驗優化的版本。

文章網址到底怎麼設定

預設使用檔案名稱,也支援 frontmatter 設定 slug,請見文檔設定,我最推薦的設定方式是這樣:

1
2
3
[permalinks]
    posts = '/posts/:slugorcontentbasename/'
    notes = '/posts/:slugorcontentbasename/'

把所有項目都放到 posts 路徑,並且使用 slugorcontentbasename 如果有 slug 就用 slug 否則使用檔案名稱,放到 posts 的原因是這樣我的目錄可以隨便改,但是永遠都會在同 URL 顯示而不會顯示前面很長的資料夾路徑名稱。

這樣也有缺點,目前 Hugo 的 PrevInSectionPrev 對這種設定都不友善,不過實際上也沒人會認真看你的部落格然後一篇一篇數文章。

我認為目前 (2025/06) 這就是最佳設定了,有需求也可以把 /posts/ 改為 /:section/ 或者 /:year/:month/:slug/ 等等,想要更進一步理解可以看文檔的 Content organization

文章連結到底怎麼設定

如果是同一層級的兩篇文章,建議直接使用 [文字](../另一篇文章),如果是層級很遠的兩篇文章,建議使用絕對路徑,例如會渲染到 https://my-site.com/path/to/另一篇文章,就使用 [文字](/path/to/另一篇文章) 來指定另一篇文章的路徑。

附帶一提,如果是在 Docusaurus Vitepress 之類的 SSG,同一層級的文章可以直接使用 [文字](另一篇文章)

使用正確的 HTML Tag

首先我們要理解 Markdown 是一個標記語言,設計目的是讓人類輕鬆方便的書寫,因此和其他標記語言相比語法規範非常不嚴格,通常我們都會透過工具把 Markdown 轉換成其他嚴格規範且好看的語言,例如 Hugo 等靜態網站生成器會轉換成 HTML,Pandoc 可以把 Markdown 轉換成 DOCX/LaTex/PDF 等等格式。

Hugo 不是 Markdown to HTML 轉換器,他的主要功能是以 Go 語言提供各種功能,例如 layouts 資料夾裡面的模板功能。真正轉換成 HTML 的其實是 Goldmark,別搞錯主角了

Markdown is Hugo’s default content format. Hugo natively renders Markdown to HTML using Goldmark.

現在我們知道是轉換成 HTML,所以在寫 Hugo Markdown 的時候心理要知道其實這是 HTML,這就帶來要說的第一個重點:任何頁面都應該只有一個一級標題,因為一個 HTML 頁面通常只會有一個 H1 標籤,而一級標題就會轉換成 H1 標籤,這也是為什麼 Hugo 預設的 markup.tableOfContents 起始等級是 2,因為大多數主題通常會自動把標題設定為 H1 標籤。

除此之外,使用圖片鏈結語法實際上也是轉換成 HTML 語法,所以這兩種寫法是等效的:

1
2
![description](img.png)
<img src="/path/to/img.png" alt="description">

但是 Hugo 渲染管道可以自動找到圖片路徑,不需要手動寫,而且 Image render hooks 支援圖片處理,例如支援 caption、圖片壓縮、自動加上 defer 等實用功能,所以基本上是一律使用 Markdown 語法。

撰寫易讀的 Markdown

Markdown 很寬鬆,但是要寫的好看好讀也是需要研究一下的,markdownlint 的作者已經整理了超多讓 Markdown 好讀好懂的語法規則,極度推薦使用,相關文章可參考 TIL:設定 Hugo 開發環境,整合 Linter 與程式碼格式化

404 頁面不支援多語言

因為這是靜態網站沒有伺服器,要找有後端處理能力的託管服務才可以達成,設定方式請見此討論

development 設定是什麼

config/development 用於開發環境的設定檔,例如我有一個 showcase 目錄可能有幾百個頁面,就可以在裡面設定 ignoreFiles 在本地開發時忽略這些檔案。

詳情請見 environment

Page bundles

bundles 應該翻譯成「包」或者「綑」,所以就是頁面包或者頁面綑綁,這知識一點屁用都沒有,我發了幾十個 PR 沒用過這觀念也是活的好好的功能全部正常,完全不懂為什麼大家都愛介紹這個。

簡單來說 bundles 就是目錄,分成 leaf bundles 和 branch bundles,leaf 就是單一文章,底下沒有子目錄或額外文章,branch 則相反,並且 branch 通常會有一個 _index.md 作為段落說明檔案。

超級實用的參數

Hugo 參數說多不多說少也不少,這裡是我整理出最實用的幾個參數:

  • -DEF: 分別代表構建 draft, expired, future,這可以避免你文章找老半天結果只是設定錯誤的問題
  • --renderToMemory: 渲染到記憶體,大小網站都適用,避免浪費硬碟讀寫壽命
  • --disableFastRender: 顧名思義
  • --bind 0.0.0.0: 綁定到本機 URL,可以透過 https://你的電腦IP:port/ 瀏覽
  • HUGO_參數: 使用環境變數覆寫任何參數,任何參數都可以覆蓋,我最常用的是 HUGO_DISABLELANGUAGES='it ja zh-cn' 快速開發避免渲染不需要的多語言,HUGO_PARAMS_DEFAULTBACKGROUNDIMAGE='/img/bg.jpg' 覆蓋 Blowfish 的白癡 SVG 背景圖片,改個文檔也可以讓 M1 MBP 風扇起飛未免太誇張。

還有不常用但是實用的參數

  • --source: 設定根目錄
  • --themesDir: 設定主題目錄,兩者結合就可以 clone Blowfish 專案並且以 hugo server --source exampleSite --themesDir ../.. 開發放在 exampleSite 的網站

頁面種類

這是從 Hugo page kind 文檔複製來的,無須說明一目了然:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
content/
├── books/
│   ├── book-1/
│   │   └── index.md    <-- kind = page
│   ├── book-2.md       <-- kind = page
│   └── _index.md       <-- kind = section
├── tags/
│   ├── fiction/
│   │   └── _index.md   <-- kind = term
│   └── _index.md       <-- kind = taxonomy
└── _index.md           <-- kind = home

layouts 目錄

layouts/_default 是用於渲染基礎頁面,所有頁面的渲染根基都在這目錄裡面。

只有 baseof.html 永遠是所有頁面的基礎,其他都不一定,我研究文檔很久才搞懂為什麼可以說出不一定這個結論,因為 Template lookup order 導致 Hugo 有非常高的互相替代邏輯,因此沒辦法說哪個檔案就一定是渲染哪種頁面。不過基本上還是可以理解成

  • single.html: 所有 page 頁面
  • list.html: section 頁面
  • taxonomy.html: 顯示所有的標籤頁面
  • term.html: 顯示個別標籤的頁面

並且請注意,這個資料夾結構在 0.146.0 就被大改,我只有一半認同這個改版,因為破壞性太大了,帶來的優勢連文檔都擠不出幾個字描述,又帶來新舊交替的混亂局面,大部分文檔都是舊版,又有部分使用新版說明。

_markup 目錄

此目錄用於自訂特定元素的渲染管道 (render hook),例如圖片、heading 或程式碼塊等等,請善用此目錄,不要手動修改 .Content,這會讓程式難以理解和維護。

Hugo 文檔寫了很詳細的用法,不過支援的還是滿少的,加上 passthrough(啥也不做)也才七種 render hook 可以自定義,目錄 (TableOfContent)、Docusaurus/Vitepress 的 ::: 語法看起來也沒辦法支援。

變數

沒有要講什麼特別的,基本上就是文檔簡單介紹了 go 語法,以及小寫的 page site 可以直接存取全局變數,所以不要再寫一堆 dollar sign,真的看的很痛苦

便箋 Scratch Pad

hugo.Store.PagenewScratch 都有 scratch pad 功能,文檔是這樣介紹的:

Returns a globally scoped “scratch pad” to store and manipulate data.

為什麼要寫這種奇怪的內容?直白的寫全局字典不是很好嗎???

基本上不會用到這個功能,除非寫了一個很厲害跨多文件的變數才會需要他來幫你記變數,我觀察了一堆專案基本上沒幾個真正需要使用 scratch,go 內建的 dict 就可以清楚易懂的解決,不需要透過 Hugo scratch 額外一層呼叫和麻煩的語法。

模板

template

在開發時請務必善用 define template 功能,這是 Hugo 唯一一個類似函式的東西,否則會寫出這種根本看不懂邊界在哪裡的妖怪,前面妖怪的修復版本可以對照我使用 inline template 寫的版本,瞬間變的清晰明確。

附帶一提,筆者在深度考慮後認為 inline template 只適用於同一檔案模組化和重用,雖然文檔說這是全局可用,但是在全局用 inline template 會讓維護變的很困難,有這種需求直接建一個 layouts/_partials/functions 就好了,不要用 inline 方式。

partial

partial 和 template 最大的差別是 partial 可以選擇回傳變數或者直接渲染 HTML 內容,template 只能直接渲染 HTML。

不過實在很難跟人推薦使用 partial 作為函式回傳變數,因為 partial 沒辦法清楚定義輸入輸出,也沒有 early-return/goto 等等機制,每個 partial 也限制只能有一個 return,這非常大幅度限制了維護性和可讀性。

block

block 定義模板後原地立刻執行,只是一個縮寫,使用頻率屈指可數的語法糖。

語法高亮

對於 codeblock 的語法高亮,進行深度研究之後得出幾個結論:

  1. 絕對不要自己搞 shiki,非常痛苦,難以整合,請當作根本沒有 shiki 的存在並且等待哪天官方支援
  2. 極度不推薦的原因是維護痛苦,並且 Chroma 已經足夠好相較下無明顯優勢,會覺得 Chroma 不夠好通常是因為主題亂搞 CSS,主題亂搞在下一點說明
  3. 主題開發者絕對不要把 syntax highlighting 寫死在檔案的其中一部分,這會讓用戶非常難以自定義,也盡量不要刪減 Chroma class,而這兩個問題 Blowfish 主題兩者都做了
  4. 正確的語法高亮、無閃爍的亮暗模式切換、無 JS 環境都支援載入的方案請見我寫的 Blowfish refined
  5. 我在 Blowfish refined 使用的方式能允許用戶以 hugo gen 自由自定義任何 highlighting CSS:
1
2
hugo gen chromastyles --style=github > assets/css/components/chroma-light.css
hugo gen chromastyles --style=github-dark > assets/css/components/chroma-dark.css

我自認這是最適合 Hugo 生態系,自定義最輕鬆,負擔最低最不需要手動維護的方式,沒有方式比這個好。

開發環境

沒有設定開發環境那你的視力很好欸,請見 TIL:設定 Hugo 開發環境,整合 Linter 與程式碼格式化 有完整說明,並且等待官方支援 gohtml 從根本解決問題。

CSS 框架選擇

絕對不要在 Hugo 中使用 Tailwind,我寫了 4500 字的文章全力抨擊 Hugo + Tailwind。

載入評論