告別肥大的資料庫,回歸純粹的寫作體驗
當你要建置一個知識付費平台時,最讓人頭痛的問題通常是:「我的文章要存在哪裡?要怎麼排版?」 傳統的做法是搭建一個包含所見即所得編輯器 (如 CKEditor 或 TinyMCE) 的 CMS (內容管理系統),然後把使用者寫好、帶有一大堆 HTML 標籤的「髒字串」塞進關聯式資料庫裡。 這種做法有三個致命缺點:
- 排版容易跑版:特別是當你貼上程式碼 (Code Block) 時,常常會亂成一團。
- 資料庫過於肥大:幾十萬字的圖文內容會嚴重拖累資料庫查詢速度,且遷移困難。
- 沒有版控機制:你很難去追蹤一篇文章過去修改了什麼,不小心刪除就沒了。
這就是為什麼在 Vibe Tutor 中,我們徹底拋棄了傳統 CMS,擁抱了工程師最愛的 Markdown 檔案系統。
什麼是 Markdown?
Markdown 是一種輕量級標記式語言,你每天在寫 GitHub 的 README.md 時都在用它。
利用幾個簡單的符號 (如 # 代表標題,** 代表粗體,``` 代表程式碼區塊),你就能寫出結構清晰、乾淨俐落的文件。
更棒的是,Markdown 檔案就是純文字檔。這代表:
- 它不佔什麼儲存空間。
- 它可以完美地使用 Git 進行版本控制 (Version Control)。
- 你可以使用你最習慣的 IDE (如 VS Code, Cursor) 來撰寫課程,享受最強大的補齊與快捷鍵。
Vibe Tutor 的動態渲染魔法
那麼問題來了,我們要怎麼把放在伺服器資料夾裡的 .md 檔案,變成使用者畫面上漂亮的網頁?
這就是 Next.js 搭配 Node.js 生態系展現神威的時候了。
請打開我們的課程核心檔案:src/app/courses/[...slug]/page.tsx。你會看到我們導入了幾個強大的開源套件:
1. fs 與 path (Node.js 內建模組)
首先,我們利用動態路由傳進來的 slug 參數,拼湊出真實的檔案路徑,並用 fs 將它讀進記憶體中。
import fs from "fs";
import path from "path";
// 假設網址是 /courses/vibe-tutor-web/01-intro
// slug 陣列就是 ['vibe-tutor-web', '01-intro']
const dirPath = path.join(process.cwd(), "content", "courses", ...slug);
const filePath = dirPath + ".md";
const fileContents = fs.readFileSync(filePath, "utf8");
這段程式碼會在 Server 端執行,所以它能直接存取伺服器的硬碟檔案系統!
2. gray-matter (前置資料解析)
你有沒有注意到,我們每篇 Markdown 的最上方,都有用 --- 包起來的區塊?
---
title: "第四章:Markdown 動態渲染技術"
---
這被稱為 Front-matter,通常用 YAML 格式撰寫,用來存放這篇文章的「元資料 (Metadata)」,例如標題、作者、發布日期等。
我們利用 gray-matter 套件,可以輕鬆地將文字檔拆分成「資料區」與「內容區」:
import matter from "gray-matter";
const { data, content } = matter(fileContents);
// data.title 就會是 "第四章:Markdown 動態渲染技術"
// content 就是底下的 Markdown 本文
3. remark 與 remark-html (Markdown 轉 HTML 引擎)
拿到純粹的 Markdown 本文後,最後一步就是把它翻譯成瀏覽器看得懂的 HTML。
我們使用了 Unified 生態系中最著名的 Markdown 處理器:remark。
import { remark } from "remark";
import html from "remark-html";
const processedContent = await remark()
.use(html, { sanitize: false })
.process(content);
const contentHtml = processedContent.toString();
就這樣,contentHtml 變成了一長串標準的 HTML 字串!
4. dangerouslySetInnerHTML 與 @tailwindcss/typography
最後,我們要把它塞進 React 元件裡。在 React 中插入原始 HTML 字串,必須使用 dangerouslySetInnerHTML 這個特殊的屬性。
<article className="prose prose-invert prose-blue max-w-none">
<div dangerouslySetInnerHTML={{ __html: contentHtml }} />
</article>
這時候你可能會想:這些被轉換出來的 HTML (例如 <h1>, <p>, <code>),因為沒有掛上 Tailwind 的 class,不就變得毫無樣式了嗎?
別擔心!這就是 @tailwindcss/typography 插件的威力!
我們在 className 裡面加上了 prose,這個插件會自動幫底下所有的 HTML 元素套上最適合閱讀的排版樣式 (行距、字體大小、清單縮排)。
而 prose-invert 則是告訴它目前是暗黑模式,字體要反白處理;prose-blue 則是設定所有的超連結跟引號要使用藍色調。
動態目錄生成系統
除了單篇文章的渲染,Vibe Tutor 還具備「自動生成目錄」的神奇功能。
如果你在網址列只輸入 /courses/vibe-tutor-web,系統會發現這是一個資料夾,而不是一個 Markdown 檔案。
這時候,我們的程式會觸發另一段邏輯:
- 使用
fs.readdirSync掃描該資料夾下所有的.md檔案。 - 將檔案依照字母順序排列 (這也是為什麼我們檔名要用
01-,02-開頭)。 - 讀取每個檔案的
gray-matter抓取title。 - 在畫面上動態產生帶有章節編號的目錄連結清單。
這意味著,身為站長的你,只要把新的 Markdown 檔案丟進資料夾,目錄就會自動更新,文章就自動上架了! 這就是 Markdown 系統帶來的極致效率。
在下一章,我們將探討這套系統最關鍵的商業邏輯:「Freemium 權限隔離機制」。如何讓前幾章免費吸引人,後面再把內容鎖起來收錢?敬請期待!