前言

你敢信?我這個個人網站的架設除了域名之外都是免費的!畢竟這只是我打算拿來發廢文的網站,能當免費仔就當免費仔。下面來講我怎麼搭建這個網站的。

框架選擇

既然要免費,那就不能選擇像是 WordPress 那種 SSR(Server-Side Rendering)的框架,畢竟要部屬到主機上面就要錢了,雖然有些雲端平台有提供免費額度,但隨著瀏覽人數變多勢必要花錢。因此選擇 SSG(Static Side Generation)的框架然後部屬在有提供免費 Static Site Hosting 的平台更為合理。

我之前的網站是使用 Gatsby 這個框架,原因也很簡單,因為我非常熟悉 React ,想說之後要客製化比較簡單,但後來發現我實在沒時間維護一堆 Typescript 和 Javascript,導致後來我都很懶得更新我的個人網站。因此這次我選擇使用 Hugo ,維護少量的 Go Template 至少比較輕鬆,而且我剛好也會 Golang。

版型選擇

Hugo 的官網提供了許多版型可以選擇 ,我這次選擇的是 hugo-PaperMod 這個版型。版型通常會提供下載步驟,照著做就行了。

1
2
3
4
hugo new site personal-website --format yaml
cd personal-website
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
git submodule update --init --recursive

設定

這個就只能照著不同的版型提供的文件去慢慢設定成自己想要的樣子了,下面只講一些比較難設定的東西。

多語言

我選用的這個版型有支援多語言,但是地方沒有翻譯好,這個就沒辦法了,只能去看原始碼自己找辦法解決,或是可以去搜尋 issues 看看有沒有人提出解法,例如我就找到了一些解法:

最後修改時間

同樣是去 issues 裡面搜尋解法

中文字體

通常版型都不會特別對中文字體做設定,所以中文字會很醜。我比較常用 Web font 的方式解決,比較方便,這裡使用 Google 的 Noto Sans Traditional Chinese 這個字體。

首先必須先去複製字體的 embed code,點選 Get fonts

Get Font from Noto Sans

之後點 Get embed code

Get embed code from Noto Sans

然後點 copy code

Noto Sans copy code

接著需要去版型的原始碼裡面找一下要插入 HTML tags 到 head 底下要插入在哪裡,經過搜尋之後發現是 這個檔案 ,於是我們必須創一個檔案叫做 layouts/partials/extend_head.html 然後把剛才複製的內容寫進去。

再來需要去版型的原始碼裡面找一下控制字體的 css 在哪,經過搜尋之後 我發現是這一行

創一個檔案叫做 assets/css/extended/custom-font.css 然後寫入以下內容:

1
2
3
4
body:lang(zh-tw) {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", "Noto Sans TC", sans-serif;
}

注意這一段就是直接從原本的程式碼抄過來,加上 body:lang(zh-tw) 的 selector 去覆蓋掉繁體中文的字體風格,但因為我的文章是中英文混雜的,為了不改變到英文字體,所以 "Noto Sans TC" 要放在 fallback font 的前面,也就是 sans-serif 的前面。

留言板

因為我們的網站是 Static Site,因此必須依靠外部服務來提供留言板功能,最有名的應該是 Disqus 。但這個服務的免費方案會有一堆廣告顯示在網站上,我覺得不是很好,因此最後選擇使用 giscus ,這是使用 GitHub Discussions 當作留言的存放地方,唯一缺點是不像 Disqus 可以讓使用者用 Google 或社群媒體帳號登入留言,但我的文章目前應該都會是技術文章,看技術文章的人應該都有 GitHub,所以問題不大。

比較麻煩的是我必須讓這個留言板支援多語言和 light、dark mode 切換,所以必須自己寫一點 Javascript,最後大概長這樣(一些敏感資訊已被替換掉)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<div id="giscus-script" />

<script>
  let lang = document.documentElement.lang;
  let category = "English Comments";
  let categoryId = "<your-category-id>";
  if (lang === "zh-tw") {
    lang = "zh-TW";
    category = "Traditional Chinese Comments";
    categoryId = "<your-category-id>";
  }

  let theme = localStorage.getItem("pref-theme");
  if (theme !== "light" && theme !== "dark") {
    theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light";
  }

  const giscusAttributes = {
    "src": "https://giscus.app/client.js",
    "data-repo": "<your-repo>",
    "data-repo-id": "<your-repo-id>",
    "data-category": category,
    "data-category-id": categoryId,
    "data-mapping": "pathname",
    "data-strict": "1",
    "data-reactions-enabled": "1",
    "data-emit-metadata": "0",
    "data-input-position": "top",
    "data-theme": theme,
    "data-lang": lang,
    "data-loading": "lazy",
    "crossorigin": "anonymous",
    "async": "",
  };
  const giscusScript = document.createElement("script");
  Object.entries(giscusAttributes).forEach(([key, value]) =>
    giscusScript.setAttribute(key, value)
  );
  document.getElementById("giscus-script").appendChild(giscusScript);

  function setGiscusTheme(theme) {
    if (giscusScript) {
      giscusScript.remove();
      const newGiscusScript = document.createElement("script");
      Object.entries(giscusAttributes).forEach(([key, value]) =>
        newGiscusScript.setAttribute(key, value)
      );
      newGiscusScript.setAttribute('data-theme', theme); // Set the new theme
      document.getElementById("giscus-script").appendChild(newGiscusScript);
    }
  }

  function handleStorageChange() {
    const theme = localStorage.getItem('pref-theme');
    setGiscusTheme(theme);
  }

  window.addEventListener("storage", (event) => {
    if (event.key === 'pref-theme') {
      handleStorageChange();
    }
  });

  // Override the localStorage setItem method to detect changes in the same browsing context
  const originalSetItem = localStorage.setItem;
  localStorage.setItem = function(key, value) {
    originalSetItem.apply(this, arguments);
    if (key === 'pref-theme') {
      handleStorageChange();
    }
  };
</script>

部屬

Static Site Hosting 的平台很多,包含 GitHub PagesNetlifyCloudflare Pages 等,我選用 Cloudflare Pages,原因很簡單,因為他的免費方案的限制最少,甚至沒有頻寬限制。

部屬非常簡單,照著 官方的文件 ,連接 GitHub repo 之後,build command 選擇 hugo 就行了。

CMS

每次都要坐到電腦前面,打開文字編輯器,才有辦法寫部落格,不是很方便,常見解法是用 CMS(Content Management System),以前我都是用 Decap CMS (之前叫做 Netlify CMS),但他對接 GitHub repo 不是很方便,而且 YAML frontmatter 和 body 之間不會產生空行,有時候會有點問題。所以這次我用 Tina CMS 。剛好 Tina Cloud 2 人以下是免費的。

設定 Tina CMS 其實沒有很難,照著官網設定完之後,去 Cloudflare Pages 把 build command 改成 git fetch --unshallow && npx --yes tinacms build && [ "$CF_PAGES_BRANCH" = "main" ] && hugo --minify || hugo -b $CF_PAGES_URL --minify 並且把 Environment variables 都加上去就行了。效果會像這樣:

Tina CMS

Tina CMS Edit Post

SEO

Favicon

Favicon 是網頁上面的小圖案,沒有設定的話在 Chrome 裡面就會變成一顆地球,不是很好。

Favicon Generator ,丟一個圖片上去,然後把檔案下載下來,解壓縮之後把裡面的檔案全部複製到 static 資料夾裡面。之後參考各個版型的文件要怎麼設定 favicon。

OpenGraph

OpenGraph 是你的網站被分享到社群網站的時候的的一些圖片、描述等設定。可以使用 https://www.opengraph.xyz/ 這個網站來檢查你的網站被分享到社群網站的樣子會長怎樣。

Google Search Console

把網站提交到 Google Search Console 可以加快被 Indexing 的速度。在裡面可以提交 sitemap,記得把 sitemap 也提交上去,XML 和 RSS 的 sitemap 都提交,可以再加快 indexing 的速度。

Submit Sitemap to Google Search Console

LightHouse

LightHouse 可以用來測量網站的效能、SEO 等。首先先用無痕視窗開啟網站,避免被 Chrome 插件影響。接著按 Ctrl+Shift+J 進入 Chrome Dev Tools,然後選擇 LightHouse 分頁

Open LightHouse

選擇 Desktop 然後點擊 Analyze page load

LightHouse start analyze

結果:

LightHouse Result

備註:Google Web Font 效能調校

當使用 Lighthouse 時可能會發現有一個效能問題是來自於 Google Web Font,此時可以使用 preload 的方式解決:

Google Font preload performance problem

1
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:[email protected]&display=swap" rel="preload" as="font" crossorigin="anonymous">

統計資料

要統計網頁資料,例如瀏覽人數等,最常用的是 Google Analytics ,超出本篇範圍,故不詳述。

結語

部屬一個網站其實要注意的細節不少,很多主題這篇文章也只是粗略帶過,畢竟不同的框架和版型要做的設定都會有點不太一樣。雖然看完這篇文章沒辦法直接復刻出一個網頁,但希望看完這篇文章可以讓你在部署自己的個人網站的時候去注意到這些細節。