為什麼你需要 Freemium 模式?
如果你直接在網路上賣一堂價值 3,999 元的課程,而客戶連試看的機會都沒有,他們購買的機率趨近於零。 這就是為什麼現在所有的串流平台、軟體服務 (SaaS) 都採用 Freemium (Free + Premium) 模式。 先提供部分高品質的免費內容建立信任,當使用者覺得「哇,免費的就這麼有價值,付費的一定更厲害」時,他們就會心甘情願地掏出魔法小卡。
在 Vibe Tutor 中,我們是如何實作這道能自動判斷「免費試閱」與「付費封鎖」的內容防火牆呢?
防火牆的第一道防線:路由分類
首先,我們必須讓系統知道,目前使用者正在看的是「哪一種課」?
在 src/app/courses/[...slug]/page.tsx 中,我們透過解析網址的 slug 陣列來判斷。
const courseId = slug[0]; // 例如 "vibe-tutor-web" 或 "line-bot-basics"
const chapterSlug = slug.length > 1 ? slug[1] : null; // 例如 "01-intro"
接著,我們在程式碼中定義了課程的分類矩陣:
// 永遠免費體驗的課程
const freeCourses = ["line-bot-basics", "gas-sheet-automation", "react-tailwind-portfolio", "github-vercel-deploy", "postgres-sql-basics"];
// 入門基礎課程 (開放前兩章免費)
const beginnerCourses = ["coding-101", "github-basics", "langchain-rag-basics", "nextjs-app-router", "api-postman-basics", "python-data-analysis"];
透過這些分類,我們可以寫出一段靈活的判斷邏輯:
let isFreePreview = false;
if (freeCourses.includes(courseId)) {
// 1. 完全免費專案,直接綠燈通行
isFreePreview = true;
} else if (chapterSlug) {
if (beginnerCourses.includes(courseId)) {
// 2. 入門課程,檔名如果是 01- 或 02- 開頭就放行
if (chapterSlug.startsWith("01-") || chapterSlug.startsWith("02-")) {
isFreePreview = true;
}
} else {
// 3. 商業高階專案,非常珍貴,只開放第 01 章免費試閱
if (chapterSlug.startsWith("01-")) {
isFreePreview = true;
}
}
}
有了這段 isFreePreview 變數,我們就解決了「哪些章節可以免費看」的問題。但如果章節不是免費的,我們就要進一步檢查使用者的「資料庫權限」。
防火牆的第二道防線:資料庫權限驗證
如果 isFreePreview 是 false,代表這是一篇需要付費才能看的文章。
這時候系統不能馬上擋人,因為使用者搞不好已經買過了!我們必須去 Supabase 資料庫查水表。
let hasAccess = isFreePreview;
if (!hasAccess) {
// 1. 取得登入者的資訊
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (user) {
// 2. 查詢該使用者的所有購買紀錄
const { data: purchases } = await supabase
.from("vt_purchases")
.select("item_id")
.eq("user_id", user.id);
if (purchases) {
const itemIds = purchases.map(p => p.item_id);
// 3. 終極權限判斷
if (
itemIds.includes("all-courses") || // 舊版全開 VIP 標籤
itemIds.includes("vip-all-access") || // 終極 9,999 VIP 通行證
itemIds.includes(courseId) || // 單堂課程純文字授權
itemIds.includes(`${courseId}-source`) // 單堂課程含原始碼授權
) {
hasAccess = true; // 權限核准,放行!
}
}
}
}
值得注意的是,這段程式碼是在 Server Component 執行的。這意味著:
- 絕對安全:駭客無法透過修改瀏覽器端的 JavaScript 變數 (例如把
hasAccess改成true) 來盜看文章。因為伺服器如果判斷你沒權限,根本連 HTML 都沒準備給你。 - 無延遲:資料庫查詢在伺服器端瞬間完成,然後直接回傳結果,使用者不會看到惱人的「載入中 (Loading spinner)」。
視覺與心理學把戲:模糊化處理 (Blur Effect)
當 hasAccess 為 false 時,最粗暴的做法就是直接回傳一個白畫面寫著「請付費」。但這太無聊了,也無助於銷售。
我們採用了更具誘惑力的做法:讓他們看得到,卻吃不到。
我們一樣把文章渲染出來,但是在外層容器加上 Tailwind 的模糊特效與限高:
<article className={`prose max-w-none ${!hasAccess ? 'max-h-[250px] overflow-hidden blur-[6px] select-none pointer-events-none opacity-40' : ''}`}>
<div dangerouslySetInnerHTML={{ __html: contentHtml }} />
</article>
這會讓畫面上呈現出文章前幾個段落的影子,然後逐漸模糊。 接著,我們在文章上方蓋上一個巨大、精美的「解鎖完整教學內容」提示框:
{!hasAccess && (
<div className="absolute top-0 left-0 w-full h-[600px] flex flex-col items-center pt-[100px] px-6">
<div className="glass p-8 rounded-3xl border border-primary/20 ...">
<Lock className="w-8 h-8 text-primary" />
<h3 className="text-2xl font-bold">解鎖完整教學內容</h3>
<p>本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析...</p>
<Link href="/pricing" className="...">查看付費方案</Link>
</div>
</div>
)}
這種「欲擒故縱」的設計,利用了心理學上的損失規避 (Loss Aversion) 與好奇心差距 (Curiosity Gap)。使用者明明已經看到了大綱,滑到最精彩的地方卻被模糊掉,這時候點擊「查看付費方案」的轉換率將會大幅飆升!
學會了如何建立堅不可摧的內容防火牆,下一步,我們要來看看如何讓使用者建立帳號。下一章,我們將揭開 Supabase 身份驗證的神秘面紗。