Next Book:用資料夾當資料庫的 GitBook 風格線上書架

2026年3月3日

實踐專案:Next Book 線上書架

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,這帶來了幾個問題:

  1. 網址要加上 repo 名稱:網站放在 vince115.github.io/next-book/ 底下,所以要設定 basePath,否則所有連結和資源都會指向錯誤的路徑。
  2. 動態路由要事先列出所有頁面:靜態輸出時沒有伺服器可以臨時產生頁面,每一本書、每一章都要在 generateStaticParams 裡列出來。
  3. 中文檔名:macOS 的檔案系統會把中文字存成 NFD 格式,但網址比對用的是 NFC,同一個字會被當成兩個不同的字串。解法是讀檔時一律用 .normalize('NFC') 統一格式。
  4. 圖片最佳化要關掉:Next.js 的圖片最佳化需要伺服器,靜態輸出時必須設定 images.unoptimized。

小結

這個實驗驗證了一件事:對個人筆記來說,檔案系統就是最好的資料庫。寫筆記就是寫 Markdown,發佈就是 git push,不用維護任何後端服務。

🌐 線上閱讀 | 💻 GitHub