---
# System prepended metadata

title: 把 HackMD 當做部落格後臺 feat. Next.js

---

---
image: https://hackmd.io/_uploads/ryr7ih0_u.png
---

# 把 HackMD 當做部落格後臺 feat. Next.js

:::success
沒什麼主題能寫的時候，更新一下部落格版型準沒錯

> by [我，2017](https://yukai.dev/blog/2017/03/29/new-primer-css-github-theme)
:::

就是這句話讓我現在又來發文 XD。

時間過得飛快，距離上次更新也過了三年多，但部落格文章也沒多寫幾篇。老實說也不是沒有寫，但不是放在私人的 Evernote，就是散落在 [HackMD](https://hackmd.io/@yukai) 上的程式碼片段和指令操作記錄，要直接貼出來還是有些羞恥的。

去年十月，突然覺得[舊部落格](https://yukaii.tw)版型實在用太久了，速度也有點慢、設計有點過時。發新文章時，還得在 HackMD 上寫完文章、在 GitHub Repo 建新檔案、再貼內容過去。若能打造一個更流暢愉快的工作流程，那我的文章產量一定能更高啊！~~（不要瞎掰好嗎）~~

## 改版計劃

這次的部落格改版有幾個需求：

1. **速度**。原本舊的部落格是用 [Jekyll 架設](https://github.com/Yukaii/yukaii.github.io/)，純靜態的網頁，換頁也使用了 [Turbolinks](https://github.com/turbolinks/turbolinks) 這個 Pjax 技術加速，無論是載入時間以及換頁都十分順暢。新的部落格系統希望能達到同樣的速度，甚至在這之上。
2. **構建的易用性**。原本的部落格使用原生的 GitHub Pages，另外拆出成 [octoflavor](https://github.com/Yukaii/octoflavor) Jekyll 主題。解釋一下「原生」和「非原生」GitHub Pages 的差別：
    - **原生**的 GitHub Pages 就是 GitHub 幫你跑 Jekyll build，你只要推上 markdown 就好了，也不用綁什麼 CI 服務
    - **非原生**的 GitHub Pages，就是把網頁的原始碼上傳到 `gh-pages` 分支，剩下來的 GitHub 幫你搞定。
4. **Node.js 派**。我只想 npm 裝好裝滿。
5. **良好的開發體驗**。如何方便的幫部落格擴充也是重要的一環。Jekyll 固然不錯，但是老舊的 Liquid 樣板語法，和沒有內建現代前端套件管理就輸了一大截。
6. **在 HackMD 上寫作**。業配 GOOD！

至於我要如何達成這幾項需求呢？先讓我們進入業配的環節吧 🤩

## HackMD 的團隊文章列表 API

![HackMD Public Team - DailyOops](https://i.imgur.com/QjF4EIL.png "HackMD 團隊 - Daily Oops!，存放文章的地方")

HackMD 有一個非公開的 API：`GET https://hackmd.io/api/@TEAM_PATH/overview`，回傳公開頁下所有能見的筆記。以我的部落格的團隊 [@DailyOops](https://hackmd.io/@DailyOops) 為例：

```javascript
GET https://hackmd.io/api/@dailyOops/overview

{
  "team": ...,
  "notes": [
    {
      "id": ....,
      "content": "...",
      "title": "...",
      ...
    }
  ]
}
```

有了這些資料，就可以繼續我的部落格改版計劃啦！

## 隆重介紹：Daily Oops 2.0 feat. Next.js

我選擇 [Next.js](https://nextjs.org/) 作為這次部落格改版的主要技術，最大的原因就是雖然它是 React.js 框架，但內建「靜態網站產生」功能 (簡稱 **SSG**, Static-site generation)。

SSG 保有動態資料的彈性，以及靜態網站的簡潔與速度。你可以透過 API 取得的動態資料，來產生多個靜態頁面，而且是「預先渲染」好的靜態 HTML 網頁，不會有 Cilent side React，載入後才渲染的現象。在頁面切換時又能保有 React SPA 的順暢換頁，我全都要！

對於部落格這種更新頻率相對不用即時，靜態頁面居多的場景，Next.js 可說是相當適合。會採用 Next.js 的另一個原因，是當時看到了 [notion-blog](https://github.com/ijjk/notion-blog) 這個專案，在 Notion 還沒有公開 API 的當下反解出 API，把 Notion 當做 CMS 來用，同樣搭配的是 Next.js 的 SSG 功能。

![](https://hackmd.io/_uploads/HytiiZnu_.png)

上圖就是新部落格的資料流：

1. 從 HackMD 抓取文章資料
2. 交由 Next.js 建置
3. 完成靜態網站建置

程式碼放在 [GitHub](https://github.com/Yukaii/daily-oops)，接下來給各位帶來幾個值得一提的細節。

## `getStaticPaths` 和 `getStaticProps` 的資料不互通

`getStaticPaths` 能拿到「靜態網站頁面列表」，`getStaticProps` 則是「靜態網站頁面資料」，但列表僅僅是列表。感覺還是有點抽象，我寫段簡化版的實作：

```typescript
const pathData = getStaticPaths()
assert(pathData).deepEqual([
  { params: { id: '1' }},
  { params: { id: '2' }},
])
// params 會用在 /posts/[id] 動態網址的參數

async function getPostData (postUUID: string) {
  // 問題：我們只有 id 而沒有 postUUID
  return fetch(`/api/${postUUID}`)
 

// getStaticProps 只能拿到 params 當做參數
const props = getStaticProps({ params: { id: '1' } })

function getStaticProps (params) {
  getPostData(params.id) // 無效
}


```

有個問題是「**getStaticProps 只能用網址參數當做函式參數**」。以上面的程式碼為例，取得文章內容的函式 `getPostData` 需要文章的 UUID，但我們 `getStaticPaths` 想要拿來當做網址的是數字 ID，這樣就沒辦法直接在 `getStaticProps` 呼叫 API 取得文章資料了。

我心目中理想的資料架構應該是這樣：

```javascript
const data = getStaticData() // new getStaticPath method
assert(data).deepEqual([{
  path: '/slug-1',
  data: {
    pageProp1: 'value'
  }
}])

const props = getStaticProps('/slug1')
assert(props).deepEqual({
  pageProp1: 'value'
})
```

`getStaticData` 是改版的 `getStaticPath`，你可以回傳除了 params 之外的參數，讓 `getStaticProps` 可以直接取用。

這個問題在 Next.js 的討論區 [#11272](https://github.com/vercel/next.js/discussions/11272) 有相當多的討論，目前 upvote 最高的解答是自己在硬碟上快取一份資料，在 `getStaticProps` 時透過快取過的 API 去抓資料。我也自己實作了一份： [`lib/post.js`](https://github.com/Yukaii/daily-oops/blob/main/lib/post.js)

## 設計：Primer 設計系統

| ![](https://hackmd.io/_uploads/BJDq5jCdO.jpg) | ![](https://hackmd.io/_uploads/Bys9qi0u_.jpg) |
| --------------------------------------------- | --------------------------------------------- |
| ![](https://hackmd.io/_uploads/B1WOqs0O_.png) | ![](https://hackmd.io/_uploads/HJSFqsA_O.png) |

點擊圖片可看大圖。

在設計方面，目前還是個工程師的我，這次一樣採用了 GitHub 的設計系統 Primer。Primer 近年來進展迅速，還多了暗色主題的設計，[色彩系統](https://primer.style/css/support/color-system) 開箱即用，不用自己慢慢調色。

![](https://hackmd.io/_uploads/rkTiioAOd.png "Primer 的 Color System，直接套 class 就有暗色效果啦")

## Site Logo

![](https://hackmd.io/_uploads/H1iqAs0_O.gif "這是一隻小貓熊，他很可愛，如果你沒看過，現在你看過了")

Logo 也是自己畫的，Google 了一張參考圖就直上了，反正小貓熊哪張圖片都很可愛，對吧（啾

![](https://hackmd.io/_uploads/SyWH13Cdu.png "附上 Figma 工作區")

![](https://hackmd.io/_uploads/rkwIJhC_O.png "Gif 圖也是用 Figma Plugin - GiffyCanvas 做的")

## Markdown 渲染

和 HackMD/CodiMD 一樣，繼續採用高度可擴充、社群套件豐富的 [markdown-it](https://github.com/markdown-it/markdown-it)。有興趣的朋友可以參考 [`lib/markdown.js`](https://github.com/Yukaii/daily-oops/blob/main/lib/markdown.js)。

值得炫耀的是程式碼區塊的渲染整個從零開始，高仿 GitHub 採用 Table 排版，除了有複製到剪貼簿，還有==行號標色==，效果如下：

| ![](https://hackmd.io/_uploads/HJ0he3ROd.png) | ![](https://hackmd.io/_uploads/HkVTe2R_u.png) |
| --------------------------------------------- | --------------------------------------------- |
|                                               |                                               |

為了讓程式碼區塊的標色和 GitHub 完全一致，還根據 Primer 顏色系統產生了 [GitHub 用的 Highlightjs 主題](https://github.com/Yukaii/github-highlightjs-themes)，實作上則參考了 [GitHub VSCode 的主題](https://github.com/primer/github-vscode-theme)。

![](https://hackmd.io/_uploads/BkarZ20O_.png "GitHub highlight.js Theme")
  
## 後續計劃

蛤不是改版改一改就好了哦？雖然整個網站大致已完成，但還是[少了些東西](https://github.com/Yukaii/daily-oops/issues)，像是：

- 站內搜尋
- 舊站轉移

也只能慢慢做啦！一如往常。
