之前寫了篇文章《部落格遷移至 Hugo》,提了下使用 Typecho 多年後越發感受到運維的成本之高後,將部落格遷移到了靜態部落格程式 Hugo 下。使用 Vercel + Github 可以免費搭建高效能部落格,繫結域名還能自動幫忙建立 SSL 證書。當然偷懶的話也可以直接使用預設分配的二級域名。
搭建
點選上面的按鈕快速抵達建立頁面,未登入的會需要登入,這塊直接使用 Github 登入即可。登入後第一步會讓你選擇 Vercel 賬號,這裡直接選擇 Personal Account
即可。之後會讓你輸入倉庫名稱,Vercel 會自動幫你建立並初始化該倉庫。如果你的倉庫不想讓其它人看的話,這裡可以勾選 Private Git Repository
建立私有倉庫。
下一步這塊會讓你輸入 Vercel 中專案的名稱和一些配置。這裡需要注意一下,官方提供的預設 Hugo 編譯命令會把草稿文章也生成出來。需要在 BUILD COMMAND
那開啟 OVERRIDE 按鈕後輸入 hugo --gc
進行覆蓋。
稍等片刻之後,你就可以看到飄著滿屏的綵帶慶祝你建立部落格成功叻!點選 Visit
按鈕你就可以看到你的部落格的樣子了。
由於是靜態部落格,你所有的文章都會儲存在你剛才新建的倉庫中。你可以選擇將倉庫下來修改後提交,也可以利用 Github 的線上編輯功能線上修改提交。提交之後 Vercel 會自動觸發更新,重新構建並更新你的部落格。
配置
預設每次提交 Vercel 構建完成之後都會把構建後的地址評論在你的 Github 提交下。你可以透過設定關閉該功能。另外預設 Vercel 指定的 Hugo 版本比較老了,在 Markdown 編譯過程中會發生一些異常的行為。我們可以透過配置指定最新的 Hugo 版本進行編譯。在專案根目錄下新建 vercel.json
檔案,並加入以下內容。
{
"github": {
"silent": true
},
"build": {
"env": {
"HUGO_VERSION": "0.78.1"
}
}
}
Hugo 部落格本身的配置都在 config.toml
下。預設情況下 Hugo 生成的 URL 都是 /posts/hello-world/
這種格式,不過之前做動態部落格為了做偽靜態,一般都將路由設定成了 /hello-world.html
這種格式。這種時候就需要在 config.toml
中增加 uglyurls 配置。
uglyurls = true
[permalinks]
posts = ":slug"
後面的 permalinks
配置主要是用來去除文章的 /posts
字首的。除此之外,預設的配置文章中的 HTML 是會被轉義的,對於我這種偶爾會在 Markdown 中寫 HTML 的人來說操作有點多餘。這時候可以在配置中定義它不轉義。
[markup.goldmark.renderer]
unsafe= true
域名
使用 Vercel 搭建的網站,它會預設提供一個 *.vercel.app
的二級域名,你可以直接使用這個域名訪問網站。如果你想要繫結自己的域名,也可以在後臺設定。進入網站後選擇自己的專案,選擇 Settings - Domains 進入域名配置介面,在輸入框中新增自己的域名。它會提示你需要給域名增加 A 記錄或者是 CNAME 解析。按照提示新增後後臺會自動檢測是否生效。
生效的時間視 DNS 伺服器的生效時間而定,我這邊使用 DNSPod 還挺快的,大概 30 秒之內就生效了。生效後 Vercel 會自動幫我們申請配置 SSL 證書,我們全然不用操心證書的問題。等待片刻之後我們就能直接使用新域名進行訪問了。
圖片
預設所有的靜態資源都放在 static/
目錄下。你可以將圖片放在該目錄下,例如 static/hello-world.jpg
。在文章中則直接使用 /hello-world.jpg
地址引用即可。
由於 Github 僅有單檔案小於 100M 的限制,Vercel 會將所有的資源部署到自己伺服器上。所以使用倉庫儲存的方式會非常方便和安全,而且還不損失速度。唯一美中不足的是,由於是 HTTP 路徑,在本地編寫文章的時候會不方便。如果是使用 Typora 的話可以點選標籤欄 格式 - 影像 - 設定圖片根目錄 將目錄指定到 static/
目錄解決。VSCode 的話暫時沒有倒騰出來。
其實比較好的方案是建議大家建立一個資料夾,將該篇文章和它所用到的圖片都歸置到一塊。使用類似元件化的思路管理內容,會更加方便後續的修改。
除了使用本地儲存之外,你也可以選擇使用第三方的儲存服務。免費服務的話可以試試 又拍雲 提供的聯盟計劃,按照要求申請下即可。其他家的儲存服務要麼免費時間有限,要麼有部分收費功能。第三方儲存的好處在於帶有 CDN 加速,在速度上會很方便。但目前 Vercel 的速度我覺得還挺不錯的,所以最終還是選擇了直接存倉庫。
評論
使用靜態部落格之後,評論則只能選擇第三方評論系統了。鑑於國情,國內的第三方評論服務都已名存實亡。目前比較知名的 Disqus , HyperComments 以及 來必力 都是國外的服務。HyperComments 是付費的服務就不多做討論,Disqus 是老牌服務,提供了強大的 API 對開發者較為友好。來必力則是對國內使用者友好,整合了很多國內的社交賬號登入。
除了專門提供評論的第三方服務之外,也有一部分是選擇自建評論服務。其中比較知名的是 Valine 和 Gitalk 以及 Staticman,isso。Staticman 提供了強大的 API 但是缺少介面,isso 則是需要伺服器部署,偏離了我使用靜態部落格的初衷就是不想維護伺服器的目的,所以這兩者都不多做討論。Gitalk 是使用 Github issue 進行評論資料儲存的評論指令碼,適合純技術部落格和極客使用。Valine 則是基於LeanCloud Serverless 雲端儲存進行評論資料儲存的評論指令碼,同時它還帶有漂亮的外觀,適合各類人群使用。
部落格站點上與評論相關的地方一般有三個地方:
- 首頁顯示最近評論
- 文章下顯示當前文章評論數
- 文章底部顯示該文章的評論列表以及輸入框
最開始我選擇了 Disqus 作為本站的評論系統。但是它有個問題國內正常無法訪問。後來我發現 Hugo 的模板中是可以使用 getJSON
方法調取介面的。而 Vercel 的機器本身就在海外,那我實際上可以在部落格編譯階段就獲取到輸入寫到頁面中。最近評論和評論數的顯示還是比較簡單的,而評論列表的顯示則需要折騰一下了。不過 Disqus API 的使用頻率是 1000 次/小時,當短時間部署頻繁的話可能會有超過的風險。而且釋出評論的介面沒辦法走該邏輯,還是需要一個代理服務。
最終讓我決定不使用 Disqus 的原因是它的評論沒辦法讓評論者輸入網站地址,評論列表中的評論者暱稱點選也無法跳轉到使用者輸入的網站中。這對於有著大量的老式評論資料的我來說是不太能接受的。所以基於這些種種原因,我最後又將評論遷移到了 Valine 下。
我對 Valine 的主要問題在於兩點:
- 早前使用 Valine 的時候發現了大量的 XSS 漏洞
- 荒野無燈大大反饋的“洩露使用者隱私問題”
以上兩個是非常重要的安全問題,不過我看 XSS 的問題作者已經修復,實際上對使用者輸入的所有 HTML 內容進行轉義可以規避該問題。而燈大反饋的 IP 洩露的問題,作者也透過不記錄 IP 來規避該問題。這兩個問題造成的原因其實還是因為 Serverless 。因為我想直接在編譯的時候將相關資料靜態化,所以我勢必是需要增加一個服務端來幫我進行介面的封裝,最後也能規避這些問題。
不過 Valine 不知因為什麼原因不開放原始碼,只在 Github 提供編譯後程式碼,這點除了讓我比較無語之外,改造工作也比較慢。本來我只需要將它 UI 中呼叫 LeanCloud API 的邏輯替換成我的介面邏輯即可。但是因為沒有原始碼,只能自己重新制作 UI 了。
更新:基於 Valine 衍生的帶後端評論系統已經完成 https://github.com/lizheming/... 已切換至該評論系統。2020/11/08
搜尋
缺少服務端之後,靜態部落格的搜尋功能也無法自己完成。目前比較知名的是第三方搜尋服務 Agolia,提供了免費的文章索引和搜尋的功能。除了使用第三方服務之外,還發現有一種比較簡單的做法就是將所有資料生成到一個檔案中,前端下載該檔案進行搜尋結果展示。這裡頭比較知名的是 fuse,它是一個 JS 模組,將輸入傳入之後它能幫我們快速的匹配到命中結果。
目前本站使用的是後一種方法,該方法的優點是不依賴第三方服務,自己就能完成搜尋功能。缺點是第一次搜尋的時候需要下載完整的資料,對文章比較多的網站使用者鴨梨會比較大。使用第二種方法第一步是需要先建立全量的資料索引,透過以下配置告知 Hugo 編譯時建立 index.json
索引檔案。
[outputs]
home = ["HTML", "RSS", "JSON"]
當然我們還需要為新的資料編寫生成的模板檔案,在 layouts/
下增加 index.json
檔案,並加入以下內容:
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" .Title "contents" .Plain "summary" .Summary "permalink" .Permalink "date" (.Date.Format "2006年01月02日")) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
其中 dict
後的字典可以新增多個欄位,視你 JS 渲染指令碼中需要的欄位而定。完成這些之後 Hugo 編譯就會生成 index.json
檔案了。
最後我們需要增加一個搜尋頁面,該頁面會先載入 index.json
然後使用 fuse
進行資料查詢,最後渲染成 HTML 輸出搜尋結果。具體的程式碼可以直接檢視原始碼 https://imnerd.org/search.htm... 參考本站的搜尋頁。
//以下為示例程式碼
let fuse;
async function search(text) {
if(!fuse) {
const indexData = await fetch('/index.json', {method: 'GET'}).then(resp => resp.json());
fuse = new Fuse(indexData, {...});
}
const result = fuse.search(text);
renderHTML(result, '#app');
}
其它
Hugo 靜態部落格能提供給我們發揮的空間非常多,比如說我參考 屈屈的部落格 增加了一個部落格文章釋出 >180 天的話,就在文章詳情頁黃條提醒:
提醒:本文最後更新於 313 天前,文中所描述的資訊可能已發生改變,請謹慎使用。
正常來說由於我沒有服務端,所以只能使用前端來計算當前時間與釋出時間的時間差。但其實轉念一想,編譯時也是可以拿到時間差的,只是這個時間差無法隨著時間的變化而變化。不過我只要每天編譯一次,就能解決變化的問題了。在 Vercel 專案 Settings - Git - Deploy Hooks 中你可以輸入 Hook 名稱和觸發分支建立一個 Hook URL。只要訪問該 URL 就可以觸發 Vercel 更新部落格。
{{ if eq .Type "posts" -}}
{{ $ageDays := div (sub now.Unix .Date.Unix) 86400 }}
{{ if gt $ageDays 180 }}
<p class="expired-tips"> 提醒:本文最後更新於 {{$ageDays}} 天前,文中所描述的資訊可能已發生改變,請謹慎使用。</p>
{{ end }}
{{ end }}
而在 Vercel 的 Marketplace 中,有一個 EasyCron 的服務提供了定時任務的功能。我在上面設定了每天零點訪問 Hook URL 觸發部落格更新,就這樣解決每天需要更新時間的問題。
另外屈屈部落格中的“檢視本文Markdown版本”也是個不錯的功能,在 Hugo 中也可以實現,本質是在編譯的時候順便生成一份 .md
的 Markdown 檔案即可。
首先我們需要在 layouts/
下增加 single.md
檔案,表示的是當文章頁(Single)需要匯出 .md
的資料的時候使用該模板。模板內容不用填寫,為空即可。然後在 config.toml
中新增 .md
檔案型別,並告知 Hugo 文章需要增加編譯 Markdown 檔案型別。
[mediaTypes]
[mediaTypes."text/plain"]
suffixes = ["md"]
[outputFormats.MarkDown]
mediaType = "text/plain"
isPlainText = true
isHTML = false
[outputs]
page = ["HTML", "MarkDown"]