這篇記錄這個站台是怎麼架起來的。重點不在「照著打指令」,而在幾個中文使用者特別容易踩到、但英文教學不會提的地方。
為什麼不用 WordPress
理由其實很單純:
| 面向 | WordPress | Hugo |
|---|---|---|
| 執行層 | PHP + MySQL,隨時可能有漏洞 | 純靜態 HTML,沒有可攻擊的執行層 |
| 維護 | 核心、外掛、主題都要持續更新 | 想更新才更新,不更新也不會被打 |
| 備份 | 檔案 + 資料庫 dump | 複製一個資料夾 |
| 速度 | 需要快取外掛才勉強夠快 | 天生就是最快的那種 |
| 寫作 | 網頁後台編輯器 | 本機 Markdown,用慣的編輯器 |
代價是沒有現成的後台,所有事情都得從指令列來。對習慣 terminal 的人來說這反而是優點。
一、安裝 Hugo Extended
不要用 apt install hugo。 Ubuntu 24.04 套件庫裡是 0.123.7,而現在多數主題(包含 PaperMod)都要求 0.146 以上,裝下去第一步建置就會失敗。
直接抓官方的 .deb:
| |
輸出必須同時包含版本號和 extended:
| |
extended 這個字很重要 —— 它代表這份執行檔內含 Dart Sass,大部分現代主題的 CSS 都靠它編譯。裝到非 extended 版本,主題會噴 this feature is not available in your current Hugo version。
二、建站與主題
| |
用 git clone 而不是 Hugo Modules,是因為 Modules 需要裝 Go。單機自架的情境下多一個依賴沒有好處。
三、繁體中文的三個關鍵設定
這一節是整篇的重點。
1. hasCJKLanguage = true
| |
沒開這個設定,中文文章的字數統計、閱讀時間、自動摘要會全部壞掉。
原因是 Hugo 預設用「空白」切詞。中文句子沒有空白,所以一整篇兩千字的文章會被算成 1 個字、閱讀時間 0 分鐘,摘要則可能整段吐出來或整個空白。
開啟後 Hugo 會改用 CJK 字元計數,一切就正常了。判斷有沒有生效很簡單:看文章列表的摘要是不是正常截斷。
2. 網址不要放中文
| |
Hugo 預設拿檔名當網址。如果檔名取成 我的第一篇文章.md,網址就會變成:
| |
這種網址貼到 Slack、LINE 或 Markdown 連結裡常常會斷掉,搜尋引擎收錄也不漂亮。
做法:檔名一律用英文,中文標題放在 front matter 的 title。讀者看到的是中文標題,網址則保持乾淨。
| |
檔名 hugo-nginx-letsencrypt.md → 網址 /posts/2026/09/hugo-nginx-letsencrypt/。
3. 搜尋要為中文調參數
PaperMod 的搜尋用 Fuse.js,而它的預設值是為英文設計的:
| |
兩個關鍵:
minMatchCharLength = 1—— 英文預設要求至少 3 個字元才算命中,但中文一個字就有完整語意(「網」、「站」)。維持預設會讓短詞完全搜不到。ignoreLocation = true—— Fuse.js 預設「關鍵字越靠近開頭分數越高」,這是英文標題的假設。中文長句裡的關鍵詞常出現在中段,不關掉的話會被評為不相關而濾掉。
四、Nginx 設定
站台原始碼放 /var/www/geek,但 Nginx 的 root 要指向 public/ 子目錄 —— 這樣 content/、themes/ 這些原始檔就不會被對外提供。
| |
先只開 80,讓 certbot 有辦法做 HTTP-01 驗證。
五、Let’s Encrypt
| |
certbot 的 nginx 外掛會自動改寫設定檔,補上 443 區塊、憑證路徑和 80 → 443 轉址。
申請前務必先測 80 埠
這是最常見的失敗點。Let’s Encrypt 對失敗次數有上限,連續失敗會被鎖一小時,所以要先確認再送出。
放一個測試檔:
| |
然後從外部網路開 http://你的網域/.well-known/acme-challenge/ping。
用手機關掉 Wi-Fi、改行動網路測是最快的方法。從伺服器自己 curl 自己的公網 IP 不算數 —— 多數家用路由器不支援 hairpin NAT,就算轉發設定正確也會連不上,測了只會誤導自己。
如果外部確實連不通,很可能是 ISP 封鎖對外 80 埠(台灣家用線路頗常見)。這種情況要改走 DNS-01:用 DuckDNS 的 API token 寫 TXT 記錄來驗證,繞過 80 埠。
續期
Ubuntu 的 certbot 套件會自帶 systemd timer,不需要自己寫 cron:
| |
--dry-run 會完整跑一次模擬續期,通過就代表三個月後會自動更新。
六、日常寫作流程
| |
--cleanDestinationDir 會清掉已刪除文章留在 public/ 的殘骸。沒加這個參數,刪掉的文章網址還是會活著。
小結
真正花時間的不是安裝,是那三個中文設定。hasCJKLanguage 沒開,站台看起來會「能動但哪裡怪怪的」——字數是 0、摘要是空的,而且錯誤訊息不會告訴你原因。這種坑值得先知道。