Next Book:用資料夾當資料庫的 GitBook 風格線上書架
2026年3月3日
實踐專案:Next Book 線上書架

為什麼做這個專案
讀書時喜歡把重點整理成筆記,但散落在各處的筆記很難回頭翻。於是做了一個實驗:像 GitBook 一樣的線上書架,每本書一個資料夾、每一章一個 MDX 檔,推上 GitHub 就自動變成可以閱讀的網站。
目前收錄兩本書:《Clean Code》的重點整理,以及《洛克菲勒談賺錢》的中英對照筆記。
技術架構
| 項目 | 使用技術 |
|---|---|
| 框架 | Next.js 16(App Router)+ React 19 + TypeScript |
| 內容 | MDX(next-mdx-remote)+ gray-matter |
| 程式碼高亮 | rehype-pretty-code + shiki |
| 樣式 | Tailwind CSS + Typography 外掛,支援深色模式 |
| 部署 | 靜態輸出,透過 GitHub Actions 部署到 GitHub Pages |
用資料夾當資料庫
不用 CMS,也不用資料庫,所有內容都是檔案:
content/books/
├── clean-code/
│ ├── meta.json ← 書名、作者、簡介
│ ├── 01-intro.mdx
│ ├── 02-meaningful-names.mdx
│ └── 03-functions.mdx
└── rockefeller-mk-momey/
├── meta.json
├── 01-man-and-money.mdx
└── …
新增一本書,只要開一個資料夾、放一個 meta.json;新增一章,只要多一個 MDX 檔。
章節順序直接寫在檔名前面。讀取時用正規表示式拆出「順序」和「網址用的 slug」:
// "01-intro.mdx" -> order: 1, slug: "intro"
const match = fileName.match(/^(\d+)-(.+)\.mdx$/);
const order = match ? parseInt(match[1], 10) : 999;
const slug = (match ? match[2] : fileName.replace(/\.mdx$/, '')).normalize('NFC');章節標題優先讀 MDX 開頭的 frontmatter,沒寫的話就從 slug 自動轉成標題。
閱讀體驗的小功能
- 側邊目錄:每本書的章節列表,標示目前讀到哪一章,可以收合
- 程式碼高亮:用 shiki 上色,看起來和編輯器一致
- 朗讀:一鍵用瀏覽器內建的語音朗讀整章,會自動判斷內容是中文還是英文
- 列印:一鍵列印目前的章節
- 深色模式
朗讀功能完全不需要後端,靠的是瀏覽器內建的 Web Speech API:
const text = (element as HTMLElement).innerText;
const utterance = new SpeechSynthesisUtterance(text);
// 內容有中文就用中文語音,否則用英文
const hasChinese = /[一-龥]/.test(text);
utterance.lang = hasChinese ? "zh-TW" : "en-US";
window.speechSynthesis.speak(utterance);部署到 GitHub Pages 踩到的坑
GitHub Pages 只能放靜態檔案,所以整個網站用 output: "export" 輸出成純 HTML,這帶來了幾個問題:
- 網址要加上 repo 名稱:網站放在
vince115.github.io/next-book/底下,所以要設定basePath,否則所有連結和資源都會指向錯誤的路徑。 - 動態路由要事先列出所有頁面:靜態輸出時沒有伺服器可以臨時產生頁面,每一本書、每一章都要在
generateStaticParams裡列出來。 - 中文檔名:macOS 的檔案系統會把中文字存成 NFD 格式,但網址比對用的是 NFC,同一個字會被當成兩個不同的字串。解法是讀檔時一律用
.normalize('NFC')統一格式。 - 圖片最佳化要關掉:Next.js 的圖片最佳化需要伺服器,靜態輸出時必須設定
images.unoptimized。
小結
這個實驗驗證了一件事:對個人筆記來說,檔案系統就是最好的資料庫。寫筆記就是寫 Markdown,發佈就是 git push,不用維護任何後端服務。