Skip to content
HiDomesticCatPublic

About

My blog (main article published)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

hicat0x0 blog

于京平(hicat0x0)的技術部落格原始碼。 線上網址:https://blog.hicat0x0.uk

項目 內容
靜態網站產生器 Hugo Extended 0.145.0(CI 固定版本)
主題 hugo-coder,直接放在 themes/(非 submodule)
語言 繁體中文 /zh/(預設)、English /en/
部署 push 到 main → GitHub Actions 建置 → 提交 docs/ → GitHub Pages
自訂網域 static/CNAME → blog.hicat0x0.uk

目錄


快速開始

1. 安裝 Hugo Extended

必須是 Extended 版本(主題用 SCSS,一般版會建置失敗)。 建議與 CI 對齊到 0.145.0。

hugo version
# 應包含 "+extended",例:hugo v0.145.0+extended

若手邊沒有套件管理器,也可以用 npm 取得同版本的二進位檔:

npm install hugo-extended@0.145.0

2. 取得原始碼

主題已經 vendored 在 themes/hugo-coder,git clone 之後不需要再 submodule update。

git clone https://github.com/HiDomesticCat/blog.git
cd blog

3. 本機預覽

./build-and-deploy.sh serve

要用與 CI 相同的參數做一次完整建置檢查:

./build-and-deploy.sh build

這支腳本不會動 git、也不會寫 docs/。部署是 CI 的工作,見下方部署流程。


目錄結構

blog/
├─ config.toml                    # 全站設定(多語、選單、主題參數)
├─ content/
│  ├─ zh/                         # 繁中內容
│  │  ├─ _index.md                #   首頁 front matter(內容不會顯示,見下方說明)
│  │  ├─ about.md / projects.md / contact.md
│  │  └─ posts/                   #   文章
│  └─ en/                         # 英文內容(結構同上)
├─ layouts/                       # 覆寫主題模板(只放有改的檔案)
│  ├─ 404.html                    #   404 頁:自動嘗試另一語系
│  ├─ robots.txt                  #   加上 Sitemap 指向
│  ├─ _default/single.html
│  ├─ posts/single.html           #   文章頁(已接上 TOC)
│  └─ partials/
│     ├─ head.html                #   複製自主題 + 加上 hreflang
│     ├─ page.html                #   一般頁面(已接上 TOC)
│     ├─ toc.html                 #   目錄,主題本身沒有
│     ├─ home/author.html         #   首頁:名字 + 一句話(取代關鍵字清單)
│     └─ head/custom-icons.html   #   只輸出實際存在的 icon
├─ i18n/                          # 專案層級翻譯字串(與主題的 i18n 合併)
│  ├─ zh.toml
│  └─ en.toml
├─ assets/                        # ★ 走 Hugo 資產管線(會被 minify + fingerprint)
│  ├─ css/custom.css              #   自訂樣式(實際生效的就是這份)
│  ├─ js/custom.js                #   自訂腳本
│  ├─ js/coder.js                 #   覆寫主題的 coder.js,修深淺色切換
│  └─ images/avatar-source.jpg    #   頭像原始圖(不會發佈,只用來重新產生圖示)
├─ static/                        # 原樣複製到網站根目錄
│  ├─ CNAME                       #   自訂網域
│  ├─ index.html                  #   / → /zh/ 轉址頁
│  ├─ favicon.ico                 #   圖示(見「頭像與圖示」)
│  ├─ site.webmanifest
│  └─ images/                     #   avatar.png、favicon-*.png、apple-touch-icon.png…
├─ themes/hugo-coder/             # 主題(vendored)
├─ docs/                          # ★ 建置產物,由 CI 自動提交,不要手改
└─ .github/workflows/deploy.yml   # 建置與部署

assets/ 與 static/ 的差別(重要)

目錄 處理方式 用途
assets/ 經 Hugo 資產管線(resources.Get → minify → fingerprint) 自訂 CSS/JS
static/ 原樣複製,不處理 CNAME、圖片、轉址頁

hugo-coder 是用 resources.Get 讀 customCSS / customJS 的, 只會在 assets/ 底下找。放到 static/ 不但不會生效,還會被原樣發佈成沒人引用的死檔案。


撰寫內容

新增文章

hugo new zh/posts/my-article.md

Front matter 範例:

+++
title = "文章標題"
date = 2026-08-23
slug = "my-article"            # ★ 一定要寫,理由見下
description = "給搜尋引擎與社群分享卡片看的摘要"
tags = ["tag1", "tag2"]
categories = ["技術"]
# toc = false                  # 單篇關閉目錄(預設吃全站設定)
# draft = true                 # 草稿不會發佈
+++

slug 一定要寫。 網址規則是 permalinks.posts = "/posts/:slug/"。沒有 slug 時 Hugo 會拿中文標題去組網址, 產生像 /zh/posts/在-android-上使用-rtl-sdr-v4完整入門與進階教學/ 這種百分號編碼、 不利於分享與 SEO 的路徑。

如果要改既有文章的 slug,記得用 aliases 保留舊網址:

aliases = ["/zh/posts/舊的網址/"]

aliases 需要含語言前綴(/zh/...),否則產生的轉址頁會落在網站根目錄。

標題層級

正文請用 Markdown 的 ## / ###,不要用純文字當小標。 標題會決定目錄、錨點與 SEO 結構。

圖片

照片多的文章請用 Page Bundle,圖片跟文章放在一起,整包好搬好刪:

content/zh/posts/my-article/
├─ index.md
├─ 01-something.jpg
└─ 02-other.jpg

⚠ Page Bundle 底下的每個檔案都會被發佈,包含子目錄。沒有用到的照片不要留在裡面 (放進 unused/ 也沒用,一樣會被複製到 docs/)。

單張圖用 Hugo 內建的 figure 短代碼,才會有正確的 <figure> / <figcaption> 結構:

{{< figure src="01-something.jpg" alt="給螢幕閱讀器與圖片載入失敗時看的" caption="圖說" >}}

多張圖併排用 gallery(layouts/shortcodes/gallery.html):

{{< gallery >}}
{{< figure src="a.jpg" caption="第一張" >}}
{{< figure src="b.jpg" caption="第二張" >}}
{{< figure src="c.jpg" caption="第三張" >}}
{{< /gallery >}}
  • cols="2" 固定欄數;不給就依容器寬度自動決定,窄螢幕一律單欄
  • ratio="16 / 9" 調整每格比例,預設 3 / 4(手機直拍的比例)
  • 格子裡的圖會裁切成一致比例好排版,但點開看到的仍是完整原圖

所有內文圖片都可以點擊放大(lightbox,assets/js/custom.js)。 支援 Esc/點背景/右上角 ✕ 關閉,也能用鍵盤操作。 包在連結裡的圖片(例如單位 logo)不會被攔截,維持原本的連結行為。

全站共用的圖片(頭像、OG 圖)才放 static/images/,用 /images/foo.png 引用。

照片發佈前的處理

手機照片直接放上去有三個問題:EXIF 帶著機型與拍攝時間、單張 3–5 MB、 可能拍到別人的臉或螢幕上的機敏資訊。建議的處理方式(需要 Pillow):

python - <<'PY'
from PIL import Image
import os, glob
os.makedirs('processed', exist_ok=True)
for f in sorted(glob.glob('*.jpg')):
    im = Image.open(f).convert('RGB')      # convert 會丟掉 EXIF
    im.thumbnail((1600, 1600), Image.LANCZOS)
    im = im.quantize(colors=256, method=Image.MEDIANCUT, dither=Image.NONE).convert('RGB')
    im.save(os.path.join('processed', f), 'JPEG', quality=86, optimize=True, progressive=True)
PY

要遮蔽局部(螢幕、識別證、車牌)時,高斯模糊之後再像素化一次, 單純模糊有機會被還原:

region = im.crop(box)
region = region.filter(ImageFilter.GaussianBlur(radius=max(region.size)//28))
small  = region.resize((region.width//48, region.height//48), Image.BILINEAR)
im.paste(small.resize(region.size, Image.NEAREST), box)

驗證 EXIF 真的清掉了,除了用 API 檢查,也直接搜位元組:

grep -c "Xiaomi" processed/*.jpg   # 應該全部是 0

程式碼

用圍欄語法並標明語言:

```bash
rtl_tcp -a 0.0.0.0 -p 1234
```

要行號與標記重點行,在語言後面加參數:

```go {linenos=true, hl_lines=[3-4]}
```

配色來自主題的 _syntax.scss(markup.highlight.noClasses = false, 所以 Chroma 輸出的是 CSS class,設 style 不會有效果)。 等寬字是自帶的 JetBrains Mono,見下方字體。

數學式

在建置期就渲染完成,訪客端不需要 JavaScript,關掉 JS 也看得到。 不必在 front matter 寫 math = true,有寫式子就會自動載入樣式表。

行內用 \( \):碰撞抗性是 \(2^{128}\) 而不是 \(2^{256}\)。

區塊用 $$ $$:

$$
P(\text{碰撞}) \approx 1 - e^{-\frac{k(k-1)}{2N}}
$$

⚠ 行內不要用 $ $,config.toml 也刻意沒開。 開了之後「這台設備要價 $50000 到 $60000 台幣」裡的 $50000 到 $ 會被當成一條數學式吃掉。行內程式碼與圍欄區塊裡的 $ 不受影響。

圖表

主力是 D2(d2lang.com,MPL-2.0)。 圖在建置期變成 SVG,所以訪客端零 JavaScript、沒有 CDN, 深色模式也會跟著右下角的主題切換走。

{{< d2 caption="圖說" >}}
client -> server: ClientHello
server -> client: ServerHello
{{< /d2 >}}

參數(都可省略):

參數 預設 說明
layout dagre 換 elk 可得到較整齊的分層
theme 0 淺色主題 ID
darkTheme 200 深色主題 ID
sketch false "true" 開手繪風
pad 12 邊距
caption — 圖說

⚠ 改完圖要重新渲染才看得到:

npm run diagrams

./build-and-deploy.sh(serve 與 build 兩種模式)與 CI 都會自動跑這一步, 只有直接呼叫 hugo 時要自己記得。圖沒渲染的話建置會失敗並指出缺哪一張。

產出在 assets/diagrams/,不進版控:檔名是內容雜湊,CI 會重新產生, 這樣圖永遠跟原始碼同步,你在沒有 Node 的機器上改文章也一樣能發佈。

Mermaid 保留給 D2 沒有的圖型(甘特圖、圓餅圖、mindmap):

{{< mermaid caption="圖說" >}}
gantt
    title 研究時程
    section 前期
    文獻回顧 :done, a1, 2026-09-01, 30d
{{< /mermaid >}}

它是在瀏覽器渲染的,用到的頁面才會載入那 2.5 MB(static/js/,不走 CDN)。

完整的元件範例在 content/{zh,en}/posts/tech-rendering-demo/(草稿,不會發佈)。 用 ./build-and-deploy.sh serve 預覽時看得到。


多語言

defaultContentLanguage         = "zh"
defaultContentLanguageInSubdir = true
  • 兩種語言都帶前綴:/zh/、/en/
  • 每個語言各自維護 [languages.xx.params](作者、描述、關鍵字、首頁 info 列表)與 [[languages.xx.menu.main]]
  • 同名檔案(例如 content/zh/about.md 與 content/en/about.md)會自動被視為互為翻譯, layouts/partials/head.html 會據此輸出 hreflang 給搜尋引擎

首頁的內容不會顯示

hugo-coder 的首頁(partials/home.html)只渲染頭像、作者、一句話與社群圖示, 不會渲染 content/xx/_index.md 的內文。 _index.md 的 title 與 description 仍會影響 og:title / og:description,所以還是要寫。

要改首頁那句話,是改 config.toml 的 [languages.xx.params].tagline,見下一節。


自訂樣式與腳本

[params]
  customCSS = ["css/custom.css"]   # → assets/css/custom.css
  customJS  = ["js/custom.js"]     # → assets/js/custom.js

assets/js/custom.js 目前提供:回到頂部按鈕、閱讀進度條、程式碼複製按鈕、 外部連結開新分頁、深色模式切換動畫、目錄捲動高亮、錨點平滑捲動。

assets/js/coder.js 覆寫了主題同名檔案,差別是:

  • 用 addEventListener('change', …) 取代已棄用的 addListener
  • 只有在使用者沒手動選過主題時才跟隨系統深淺色(原本會蓋掉使用者的選擇)

主題升級時要記得比對 themes/hugo-coder/assets/js/coder.js 是否有變動, 這是整份覆寫,不是 patch。

字體

用途 字體 來源
西文內文 / 標題 Inter(400 / 600 / 700) 自帶,static/fonts/
程式碼 JetBrains Mono(400 / 700) 自帶,static/fonts/
正體中文 蘋方 / 微軟正黑體 / Noto Sans TC 系統內建

兩支自帶字體都是 OFL-1.1,沒有任何 CDN 請求。 每個 @font-face 都帶 unicode-range,所以 latin-ext 只有在頁面真的出現 波蘭文、土耳其文那類重音字母時才下載,平常是 0; 中文碼位不在任何一個範圍內,因此西文字體不可能攔截中文, 中文一定落到堆疊裡的系統字型。

中文刻意不自帶:各平台都已內建高品質正體字型,自帶要付好幾 MB 與首屏延遲。

⚠ 主題原本的字體堆疊裡 一個正體中文字型都沒有(只有日文的 游ゴシック 與簡體的 Microsoft YaHei 等)。Windows 兩者都預裝, 於是正體中文被日文/簡體字型排版,而且各字型收字範圍不同 —— 啟 在 Yu Gothic 沒有(日文寫 啓)就掉到微軟雅黑, 同一個詞裡混了兩套字型。assets/css/custom.css 的 --font-sans 修正了這件事, layouts/_default/baseof.html 也把 <html lang> 從 zh 改成 zh-Hant (漢字統一碼要靠 lang 才分得出要用哪一種字形)。


目錄(TOC)

hugo-coder 沒有目錄功能,這裡自己補了 layouts/partials/toc.html。

[params]
  enableToc      = true   # 全站開關
  tocMinHeadings = 3      # 標題少於這個數量就不顯示

[markup.tableOfContents]
  startLevel = 2          # 從 ## 開始
  endLevel   = 4          # 到 #### 為止

單篇要關掉就在 front matter 寫 toc = false。

樣式在 assets/css/custom.css 的 .toc / #TableOfContents 區塊, 捲動高亮在 assets/js/custom.js。


模板覆寫

layouts/ 底下只放有修改的檔案,其餘沿用主題。目前覆寫了:

檔案 為什麼
partials/head.html 複製自主題,額外輸出 hreflang 多語連結
partials/head/custom-icons.html 主題會無條件輸出 6 個 icon 連結,其中 SVG 那兩個做不出來;改成只輸出真的存在的檔案
partials/home/author.html 首頁改成「名字 + 一句話」,取代關鍵字清單
partials/page.html 一般頁面加上目錄
partials/toc.html 新增,主題沒有目錄功能
posts/single.html 文章頁加上目錄
_default/single.html 統一標題格式
404.html 找不到頁面時自動試另一個語系
robots.txt 加上 Sitemap: 指向
shortcodes/gallery.html 新增,多張圖併排

升級主題後請檢查 partials/head.html,那是整份複製的。


首頁的一句話

首頁名字下面那一行由 params.tagline 決定,是語言層級參數,中英各寫各的:

[languages.zh.params]
  tagline = """
不被觀測的東西,會安靜地不見。
所以我習慣自己打開箱子看看。"""
  • 字串裡的換行就是斷行,所以可以寫一行,也可以寫成對句
  • 每一行各自過 markdownify,內容有被跳脫,也還能用 *強調*
  • 沒設 tagline 時,會自動退回主題原本的 params.info 清單行為

TOML 陷阱:一般的 "..." 字串裡不能有真的換行,會出現 unmarshal failed: toml: basic strings cannot have new lines 而整個設定檔載不進去。 要用三引號的多行字串 """(緊接在開頭引號後的換行會被 TOML 自動去掉)。

樣式在 assets/css/custom.css 的 .tagline 區塊。 斷行由上面的換行決定,max-width 只是安全網 —— 設太窄會搶著替英文換行, 把對句擠成三四行。


頭像與圖示

全部由 assets/images/avatar-source.jpg 產生。assets/ 底下沒被引用的檔案不會發佈, 所以原始圖留在 repo 裡只是為了日後能重新產生,不會多佔一份流量。

產生的檔案:

檔案 尺寸 用途
static/images/avatar.png 400×400 首頁頭像(顯示 200px,2× 螢幕用)
static/images/favicon-32x32.png / -16x16.png 32 / 16 瀏覽器分頁
static/favicon.ico 16/32/48 直接抓 /favicon.ico 的舊瀏覽器與爬蟲
static/images/apple-touch-icon.png 180×180 iOS 加到主畫面
static/images/android-chrome-*.png 192 / 512 site.webmanifest 引用

要換頭像,換掉 assets/images/avatar-source.jpg 後重跑(需要 Pillow):

python - <<'PY'
from PIL import Image
import os
src = Image.open('assets/images/avatar-source.jpg').convert('RGB')
# 先裁成正方形;下面的框是針對目前這張圖算出來的,換圖記得重算
sq = src.crop((26, 7, 800, 781))
out = 'static/images'
def gen(size, name, quantize=True):
    im = sq.resize((size, size), Image.LANCZOS)
    if quantize:
        im = im.quantize(colors=256, method=Image.MEDIANCUT, dither=Image.NONE)
    im.save(os.path.join(out, name), 'PNG', optimize=True)
gen(400, 'avatar.png')
gen(180, 'apple-touch-icon.png')
gen(192, 'android-chrome-192x192.png')
gen(512, 'android-chrome-512x512.png')
gen(32, 'favicon-32x32.png', quantize=False)
gen(16, 'favicon-16x16.png', quantize=False)
sq.resize((64, 64), Image.LANCZOS).save('static/favicon.ico', sizes=[(16,16),(32,32),(48,48)])
PY
  • 要裁成正方形:頭像套 border-radius: 50%,非正方形會被壓扁
  • 256 色量化:像素風配上 JPEG 壓縮雜訊很難壓,量化後 400×400 從 182 KB 降到 74 KB, 實測 RMS 差異只有 1.5%,肉眼看不出來。16/32 的小圖不量化,保持銳利

主題的 head/custom-icons.html 原本會無條件輸出 favicon.svg 與 safari-pinned-tab.svg,那兩個是向量檔、沒辦法從點陣頭像產生, 導致每次載入都噴 404。已覆寫成只輸出實際存在的檔案。


部署流程

push 到 main
   └─> .github/workflows/deploy.yml
         ├─ 安裝 Hugo Extended 0.145.0
         ├─ hugo --minify --gc --baseURL https://blog.hicat0x0.uk/
         ├─ 驗證 public/ 有 index.html、zh、en、CNAME
         └─ 複製 public/ → docs/、加 .nojekyll、commit 並 push
               └─> GitHub Pages 從 main 分支的 docs/ 提供服務
  • 部署 commit 帶 [skip ci],不會觸發第二輪建置
  • concurrency: deploy-pages 確保同時只跑一個部署,避免兩次 push 打架
  • docs/ 不要手動編輯,下一次 CI 會整個覆蓋掉
  • 也可以在 GitHub 的 Actions 頁面用 workflow_dispatch 手動觸發

GitHub Pages 設定

Settings → Pages → Source = Deploy from a branch, Branch = main,資料夾 = /docs,Custom domain = blog.hicat0x0.uk。


常見問題

啟用 customCSS / customJS 後出現 nil pointer evaluating resource.Resource.RelPermalink

檔案放錯位置了。主題用 resources.Get 載入,必須放在 assets/,不是 static/。

改了 static/css/custom.css 卻沒有任何變化

那個路徑不會被引用。實際生效的是 assets/css/custom.css。 (這兩份重複檔案已在 2026-08 移除。)

中文文章網址是一長串百分號編碼

front matter 少了 slug。補上後記得加 aliases 保留舊網址。

首頁改了 _index.md 沒反應

首頁不渲染 _index.md 內文,見多語言一節。

WARN found no layout file for "json" for kind "home"

[outputs] home 曾包含 JSON,但主題沒有對應模板。已改成 ["HTML", "RSS"]。

改了 CSS 的 font-size,字卻小到看不見

主題設了 html { font-size: 62.5% },所以 assets/css/custom.css 裡 1rem = 10px,不是 16px (body 是 1.8rem = 18px)。照一般 16px 基準的直覺寫 1.1rem 會得到 11px。檔頭有註記。

改了 config.toml 之後整個站建不起來,說 basic strings cannot have new lines

TOML 的一般字串 "..." 不能含真的換行。多行內容(例如 tagline)要用 """。

建置失敗,說 SCSS 相關錯誤

裝到非 Extended 版的 Hugo 了。hugo version 要看到 +extended。

About

My blog (main article published)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages