用 Nuxt 3 搭建一个按篇付费的博客
很多人问这个站是怎么搭起来的。这篇文章把整体架构摊开讲清楚:渲染策略、内容管线、付费墙安全模型三个核心问题。
先想清楚:付费内容放在哪里
按篇付费的博客,最容易踩的坑是把全文塞进前端 payload 再用 CSS 遮罩。右键查看源码就能白嫖,这不行。所以第一条铁律:
付费内容绝不出现在公开的 HTTP 响应里,包括 SSR 的 HTML、水合 payload、以及任何内部 API。
本站的解法是「构建期物理隔离」:源文件 articles/*.md 同时生成两份产物——
server/assets/content/
├── manifest.json # 公开索引
├── previews/<slug>.json # 预览段 HTML(可被爬虫抓取,SEO 友好)
└── full/<slug>.json # 完整 HTML(仅 entitlement API 读取)
Nitro 把 server/assets 挂载为服务端私有存储,公开 API 只读 previews/,全文只通过一条校验购买状态的接口下发。物理上不可达,而不是逻辑上隐藏。
渲染策略:SSR + 客户端解锁
页面本体走 SSR 并配合 swr 缓存,因为公开部分与用户无关;购买状态则在水合后由客户端请求 /api/articles/:slug/full 拉取。这样同一篇文章的页面可以复用缓存,付费校验只发生在数据接口上。
用一张图描述整条链路:
内容管线:markdown 是唯一事实源
写作工作流保持极简——本地写 markdown、push 到 Git 仓库,没有任何在线后台。构建脚本用 unified 生态处理渲染:
const processor = unified()
.use(remarkParse)
.use(remarkGfm) // 表格 / 脚注 / 任务列表
.use(remarkMath) // 数学公式入口
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeKatex) // 渲染 KaTeX
.use(rehypeSlug) // 标题锚点,供目录使用
代码高亮用的是 Shiki 的 vesper 主题,与整站暗色美学一致;Mermaid 图表在构建期仅留下占位符,浏览器端再实例化渲染,避免把整个图表引擎塞进首屏包。
数学公式长什么样
讨论缓存命中率时随手推导一下 Little's Law1:
其中 是系统中平均请求数, 是到达率, 是平均停留时间。它对容量规划的指导意义在于:并发数 = 吞吐 × 延迟,三个量知二求一。
小结
- 付费墙的安全靠「构建期隔离 + 服务端校验」,不靠前端遮罩
- 渲染走 SSR,付费解锁走客户端接口,缓存互不干扰
- 内容管线纯 Node 脚本,写作就是写 markdown
这套架构的数据库层(订单、购买凭证、RLS 策略)在后续的付费专栏里展开。
Footnotes
-
Little, J. D. C. (1961). "A Proof for the Queuing Formula". ↩