用 Nuxt Content 重寫我的 Hugo 部落格

用 Nuxt Content 重寫我的 Hugo 部落格

開發筆記

前言

透過 Hugo 建立好部落格後,除了覺得樣式比例有點醜之外,大致上都不錯。

Hugo 部落格

不過用下來總覺得心癢癢,畢竟目前我的技能樹是以 Vue 為主,但部落格目前用的是 Go’s html/template,維護上還是多了些心靈負擔。

畢竟框架的開發體驗相當良好,做什麼都很方便,不論是 UI 框架或是功能,大多都可以找到套件解決。

雖然也研究過要不要使用 Vue 來重寫部落格,但最大的問題是找不到理想的靜態網站生成器(Static Site Generators)。

以 Vue 生態圈來說,VuePressVitePress 可能是比較好的選擇,但我認為這兩者的結構更適合拿來寫文件,拿來做部落格還是顯得有些怪異。

但正好遇到 Rock 和我分享了 Nuxt Content 這個工具,並且也有寫成文章 - 用 Nuxt Content 重寫我的部落格,對我來說真的是受益良多。

雖然當時我完全沒有接觸過 Nuxt,不過也已經聽聞這個 Vue 的 SSR 框架許久,遷移到 Nuxt Content 也算是多一個主動學習的機會。

不過 Nuxt Content 在我初期評估下來其實 CP 值不高,雖然重新切版因為能寫 Vue,可以預想重建過程會比 Hugo 更輕鬆。但其餘像是輸出速度或頁面載入速度等等,我認為跟 Hugo 相比不會有多少優勢。

但考慮許久之後,我還是決定要遷移到 Nuxt Content。

畢竟我覺得部落格是簡單能展示自己技能的方式之一,相對於用 Hugo 寫的部落格,我相信對於目前的前端領域來說,Nuxt 寫的部落格應該是更加分一點。

還有一點就是 Hugo Theme 程式碼一打開,那模板實在看的眼花撩亂,而且還沒有程式碼 highlight 和 format,真的會讓人失去改 Code 的動力,我想重構或遷移的那天遲早都會到來。

部落格工具選擇

為了讓這次升級有具體的完成方向,我設定了幾個目標:

  • 使用並學習 Nuxt3 與 TypeScript
  • 提升專案的可維護性
  • 新增深色模式

使用 TypeScript 是因為團隊大多專案都開始在使用 TS 開發,我覺得用自己的部落格當作練習是一個不錯的開始。

  1. 我的部落格需求應該算比較單純,通常遇到的型別問題都很簡單。
  2. 畢竟是自己的部落格,我肯定不打算濫用 TS 的 as 或是 any 得過且過,遇到型別問題會想辦法處理。

而專案的可維護性這點就比較模糊,主要是修改之前寫的 Hugo Theme 真的太可怕了,過一段時間回頭看之前寫的 CSS 也是很頭痛。

改用 Vue 的話在管理元件跟 HTML 上應該會舒適很多,而這次我打算加入語法檢查和 Format On Save 工具 - 使用 ESLintPrettier

老實說這兩套我都只在公司的專案中使用,通常前輩都會先處理好環境,倒還沒有自己建構過。

總之也當作是怎麼學習使用,這樣一來程式碼品質應該能有不少提升。

CSS 處理我原本打算使用 Tailwind,最大原因也是因為公司專案正在使用。

所以一來是想要當作練習,藉此更熟悉 Tailwind 的寫法,再來就是我覺得 Tailwind 寫起來真的挺香的,維護 CSS 上真的方便不少,而且省去很多思考 class 命名的困擾。

這部分可以看 Huli 大大寫的這篇 淺談 Atomic CSS 的發展背景與 Tailwind CSS - Huli’s blog,完全能說服我放心的使用 Tailwind。

但就在我要開始建立專案的前幾天,偶然看到 Anthony Fu 這篇 重新构想原子化 CSS 後,我打算直接鬼轉改用 UnoCSS 看看,畢竟有一個效能更好的工具為何不用呢?而且看起來轉換成本也不高,確實值得一試。

因此最後就定案是這樣:

  • Framework:Nuxt3 + TypeScript
  • UI Framework:UnoCSS
  • Extension:ESLint、Prettier

過程遇到的問題

Hugo 的程式碼基本上不太能沿用,所以算是完全從零開始重寫,不過至少可以直接照現有畫面切版,省去很多思考樣式設計的時間。

其實重寫遇到的問題並不多,畢竟是熟悉的語言跟框架,大多遇到的問題幾乎都是自己對 Nuxt3 的不熟悉而造成。

但 Nuxt Content 本身也算是比較新的套件,在開發的過程中加減有遇到一些小問題,例如套件衝突、安裝直接掛掉等等…

不過這些都很快就透過文件說明或參考 github issue 解決。

而下面記錄一些我比較有印象的問題:

Nuxt Content 的路由管理邏輯

比起 Nuxt 本身用 Pages 資料夾底下的 .Vue 幫你自動處理路由,Nuxt Content 比較像是用 Markdown 當作是頁面的路由管理,詳細可以看官方文件說明:Content Directory - Nuxt Content

下面的路由結構是官方範例的一個結構:

content/
  1.frameworks/
    1.vue.md
    2.nuxt.md
  2.examples/
    1.vercel.md
    2.netlify.md
    3.heroku.md
    index.md

而且這個結構對我來說,在檢索上會遇到一些問題,例如我更偏好我的文章路由不是 /frameworks/nuxt,而是像 /posts/nuxt,那可能會將資料夾路徑設計成這樣:

content/
  1.posts/
    1.vue.md
    2.nuxt.md
    3.vercel.md
    4.netlify.md
    5.heroku.md
    index.md

但如果我又同時有分類的需求,我要如何知道 nuxt 是要歸類在 frameworks 這個分類目錄下呢?

有個作法是可以使用 front-matter,透過 YAML syntax 的語法來擴充。

並且需要在 nuxt.config.ts 中設定

nuxt.config.ts
  content: {
    documentDriven: true,
    navigation: {
      fields: ['title', 'date', 'summary', 'categories', 'tags', 'featuredImage']
    },
  },

並且再透過檢索的方式來處理

const query: QueryBuilderParams = {
  where: [{ categories: "frameworks" }],
  skip: 0,
  limit: 5,
  sort: [{ date: -1 }],
  count: 1,
};

這樣一來就能找到所有屬於 frameworks 分類的文章。

當然如果需求很複雜,限制搜尋內容的作法還有很多種,例如可以用 Mongo query syntax,又或是把資料都抓出來後,用正則表達式或是關鍵字搭配一些陣列操作解決。

但缺點就是會跟我一樣,花了大量時間在寫檢索文章的邏輯…

Document Driven

Document Driven 是 Nuxt Content 中的一個模式,他新增了 useContent(),可以更方便的取得 Markdown 的資訊,相當方便。

但我很快的發現了一個問題,使用 Document Driven 後,瀏覽器 console 中總會出現錯誤:

GET http://localhost:3000/api/_content/query?_params=%7B%22where%22:%5B%7B%22_path%22:%22/%22%7D%5D,%22first%22:true,%22sort%22:%5B%7B%22_file%22:1,%22$numeric%22:true%7D%5D%7D 404 (Document not found!)

雖然畫面上並沒有產生實際的問題,但仍然有點糟心,好在一番排查後我發現了問題。

在使用到動態路由的路徑中,必須確保每個資料夾路徑都至少有 index.md 能正常渲染內容,讓這個路由不會出現 404。

例如我希望有 /category/life/category/code 這兩個路由,我的 category 結構會像下面這樣。

content/
  - category
    - life.md
    - code.md

但這樣是會出現錯誤的,因為如果當我進入 /category 時就會出現問題,所以必須添加 index.md,最後結構就會如下:

content/
  - category
    - index.md
    - life.md
    - code.md

但我希望 /category 的頁面不是文章,又或是想導向 404 頁面,那該怎麼做?

到處求解後我才理解,開啟 Document Driven 之後,Markdown 基本上接近於 Pages 的用途。

比較理想是將 Pages 刪除後改用 Layout 來撰寫頁面結構,然後在 Markdown 中使用 layout-binding 就能解決。

content/category/index.md
---
title: 所有目錄 | Uhiko's Blog
layout: categoryHome
---

這樣只要在 categoryHome.vue 撰寫自己想顯示的內容即可。

雖然是解決了,但這跟 Nuxt 本身的邏輯有點衝突,對我這個剛學 Nuxt 的新手來說似乎剛好踩錯了坑,實在有點頭疼。

成果

Nuxt3 部落格

樣式經過調整後,感覺部落格是有比較精緻了一點。

深色模式也一併完成,這部分使用 Nuxt Color Mode 很順利的完成:

Nuxt3 部落格深色模式

其中樣式部分最花時間處理的是程式碼區塊,因為 Nuxt Content 和官方文件顯示的不同,預設是沒有樣式的,只好自行覆蓋撰寫…

部落格程式碼區塊

旁邊做了一個複製按鈕,點擊複製後也會變成勾勾,等用戶滑出範圍後就會再變回複製 icon。

部落格程式碼區塊點擊複製按鈕

此外,SPA 網頁最大問題應該是初次載入的效能表現不怎麼好,這點在我的部落格上也不例外,即使只有一篇文章也無法跑到綠燈。

部落格 PageSpeed

可改善主要原因是來自於 TBT

部落格 PageSpeed 改善建議

這部分透過壓縮、調整一些資源載入的順序來優化,畢竟部落格網站還算是相當單純的,優化過程相當順利。

部落格 PageSpeed 優化

輕輕鬆鬆就拉高分數了,但想要 100 分的話可能有點困難,我個人是覺得有綠燈即可。

不過未來這部分應該還需要再優化,畢竟我的部落格圖片放很多,最容易遇到的大概就是圖片載入造成 LCP 問題。

而且目前是用 SSG 輸出,所以無法透過 NuxtImg 優化,如果部落格的最新文章用到比較大的圖片,可能會顯著降低效能。

如果是圖片很多的文章頁面那可能就更糟了…當然未來可以考慮改為 SSR 試試。

總之經過這次重構後,我認為改用 Nuxt3 是很正確的選擇,開發體驗真的舒服不少,SPA 切換也挺香的。

UnoCSS 中用 Tailwind 的寫法也讓 CSS 處理更加容易,擺脫了以往要想 class name 跟粗心就會改 A 壞 B 的問題。

而主角 Nuxt Content 我會覺得像是更加自由一點的 VitePress,雖然其實有不少大大小小的限制或問題,但總是能靠 Nuxt 的方式解決。

因為結構的設計,我覺得用在適合 SPA 結構的網站上會比較舒服,特別就像是 Nuxt 這種文件類型的網站。

不然像我的部落格比較偏向傳統以頁面為單位切換的網站來說,反而會因為要處理資料而四處撞牆。

當然現階段最尷尬的可能還是 Document-driven,開啟後一些後續結構問題跟 Bug 實在雞肋,而且目前還屬於 experimental 功能。

我想現在的架構總有一天還是得重構一次…總之先用一陣子再來看看怎麼調整吧~