前言:为什么不用「一个框架搞定一切」?
做个人博客,常见路线有三条:
- 静态站点生成器(Hugo、Hexo):部署简单,后台体验弱
- 一体化 CMS(WordPress、Ghost):成熟省心,定制成本高
- 全栈框架一把梭(Next.js 前后台都做):技术栈统一,但 Admin 往往不如专业后台框架顺手
我这次做的 RR Blog,走的是第四条路:按「谁在用、要解决什么问题」来选技术,而不是按「我会什么就只用什么」。
| 端 | 技术 | 职责 |
|---|---|---|
公开博客 web/ | Next.js 15 + Tailwind | 文章展示、搜索、SEO、RSS |
管理后台 admin/ | Vue 3 + Element Plus + WangEditor | 写文章、分类标签、评论审核 |
API 服务 api/ | Express + MySQL + JWT | 公开读接口 + 管理写接口 |
一句话:读者侧要性能和 SEO,作者侧要熟悉的 CMS 体验,中间用 API 解耦。
核心设计思路
1. 按场景拆分,而不是按语言拆分
三端分离的核心不是「用了三种框架」,而是三种访问场景的需求完全不同:
| 场景 | 核心诉求 | 选型 |
|---|---|---|
| 公开读者 | 首屏快、SEO 好、可分享 | Next.js ISR + metadata |
| 管理员 | 表单多、权限细、编辑顺 | Vue Admin + RBAC |
| 数据服务 | 稳定、可扩展、两端复用 | Express REST API |
公开页和管理页不需要共享 UI 组件,强行统一 React 或 Vue 反而增加成本。API 层才是两端真正的交汇点。
2. 复用已有模式,而不是从零造轮子
RR Blog 不是孤立的新项目,而是把已有工程经验迁移到博客场景:
- API 层:复用
backendnode的 JWT 鉴权、分页封装、统一响应{ code: 20000, msg, data } - Admin 层:复用
vue_elmp_ts_vt的 layout、动态菜单 RBAC、useTablePage/useForm - 数据库:在 RBAC 表(
sys_user/sys_role/sys_menu)上扩展博客表,而不是另起一套用户体系
这样做的收益是:开发快、结构熟、以后维护和别的项目一致。
3. 公开接口与管理接口分离
API 路由按「是否需要登录」清晰分层:
/blog/* → 公开读(列表、详情、RSS、评论提交)
/blog/posts/* → 管理写(JWT + RBAC)
/auth/* → 登录鉴权
/system/* → 菜单、媒体上传
前台 Next.js 只调公开接口,Admin 调管理接口。权限边界在 API 层收口,前台代码里不会出现「管理员逻辑」。
4. 内容走 HTML,前后台各取所需
后台用 WangEditor 富文本,存 HTML 到 MySQL;前台用 DOMPurify 消毒后直接渲染。
- 作者:所见即所得,不用学 Markdown 语法
- 读者:样式统一,SEO 友好(标题、摘要、正文结构清晰)
- 工程:不必维护 MD → HTML 编译链
整体架构
flowchart LR
subgraph public [公开访问]
Web["Next.js 博客前台\nISR + SEO"]
end
subgraph adminPanel [管理员]
Admin["Vue 3 CMS\n富文本 + RBAC"]
end
subgraph backend [后端]
API["Express API"]
DB[(MySQL)]
end
Web -->|"GET 公开接口"| API
Admin -->|"JWT 管理接口"| API
API --> DB
这样拆的四个好处
- SEO 有保障:App Router +
generateMetadata+ sitemap + RSS,公开页走 ISR - 后台开发效率高:Element Plus 表格/表单/弹窗开箱即用,RBAC 菜单后端动态下发
- API 可复用:同一套服务供前台、后台、未来的小程序或 RSS 阅读器
- 部署灵活:三端可同机跑,也可拆到 Vercel + 云服务器 + Nginx 静态托管
项目结构
rr-blogs/
├── web/ # Next.js 公开博客 (:3000)
├── admin/ # Vue 3 CMS (:5173)
├── api/ # Express API (:3001)
├── docs/ # 中文文档(架构、API、部署)
└── docker-compose.yml
每个子项目独立 package.json,职责单一。开发时三个终端分别启动;生产环境可按域名拆分部署。
构建亮点
亮点 1:Next.js ISR + 按需刷新
公开页不直连数据库,而是通过 fetch 调 Express,并开启 ISR(默认 60 秒 revalidate):
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:3001'
export const REVALIDATE_SECONDS = 60
async function fetchApi<T>(path: string, init?: RequestInit) {
const res = await fetch(`${API_BASE}${path}`, {
...init,
headers: { 'Content-Type': 'application/json', ...init?.headers },
next: init?.next ?? { revalidate: REVALIDATE_SECONDS },
})
const json = await res.json()
if (json.code !== 20000) throw new Error(json.msg)
return json.data
}
发布后可通过 webhook 主动刷新,不必等缓存过期:
curl -X POST http://localhost:3000/api/revalidate \
-H "x-revalidate-secret: your_revalidate_secret"
思路:动态内容 + 静态性能兼得——大部分请求命中缓存,内容更新仍可主动推送。
亮点 2:每篇文章自动生成 SEO 元数据
App Router 的 generateMetadata 按 slug 拉取文章,动态生成 title、description、Open Graph:
export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.summary,
openGraph: {
title: post.title,
description: post.summary,
images: post.cover ? [post.cover] : [],
},
}
}
思路:SEO 不依赖手工维护,写文章即自带分享卡片和搜索摘要。
亮点 3:Vue Admin + 后端动态 RBAC
后台不是写死的菜单,而是从 sys_menu 表读取、按角色过滤后下发。JWT 登录后,前端根据菜单树动态渲染路由。
思路:博客虽小,权限模型按「正规后台」设计——以后加编辑、审核等角色不用改前端结构。
亮点 4:评论审核闭环
访客提交评论 → status = pending → 管理员在 CMS 审核 → 通过后前台才展示。
思路:开放评论但不开放 spam,个人博客也需要基本的内容治理能力。
亮点 5:完整的中文工程文档
docs/ 独立目录,与代码分离,包含:
- 总体方案与实施阶段
- 架构说明、数据库设计、API 参考
- 快速开始、后台指南、部署指南
思路:项目不仅要能跑,还要半年后再看仍说得清为什么这样设计。
数据流:从写文章到读者看到
sequenceDiagram
participant Admin as Vue CMS
participant API as Express API
participant DB as MySQL
participant Web as Next.js 前台
Admin->>API: POST /blog/posts/add (JWT)
API->>DB: 写入文章
Admin->>Web: POST /api/revalidate (可选)
Web->>API: GET /blog/posts/:slug
API->>DB: 查询已发布文章
API-->>Web: 返回 JSON
Note over Web: ISR 缓存后展示
- 草稿(
status = 0)不会出现在公开接口 - 详情页 HTML 经 DOMPurify 消毒后渲染
- RSS 由 API 生成,前台
/rss.xml重定向到 API 地址
数据库设计要点
在 RBAC 基础上扩展博客表,关系清晰:
| 表 | 作用 |
|---|---|
blog_post | 标题、slug、摘要、HTML 正文、封面、状态、浏览量 |
blog_category | 分类 |
blog_tag + blog_post_tag | 标签多对多 |
blog_comment | 评论及审核状态 |
sys_media | 上传图片(封面 / 正文插图) |
sys_user / sys_role / sys_menu | 管理员与权限 |
slug 全局唯一,作为 URL 标识;软删除(deleted 字段)保留数据可恢复性。
技术选型对比
| 选择 | 备选 | 理由 |
|---|---|---|
| Next.js 前台 | Nuxt、纯 Vue SSR | ISR/SEO 生态成熟 |
| Vue 后台 | React Admin、Strapi | 与现有 Vue 项目模式一致 |
| Express API | NestJS | 轻量,RBAC/上传模式现成 |
| WangEditor | Markdown | 后台 WYSIWYG,前台直渲染 HTML |
| MySQL | MongoDB | 博客 + 权限关系型建模更自然 |
5 分钟本地跑起来
# 1. 启动 MySQL
docker compose up -d
# 2. 初始化数据库
cd api && npm install && npm run db:init
# 3. 启动 API(:3001)
npm run dev
# 4. 启动 Admin(:5173,新终端)
cd ../admin && npm install && npm run dev
# 5. 启动 Web(:3000,新终端)
cd ../web && npm install && npm run dev
| 服务 | 地址 |
|---|---|
| 公开博客 | http://localhost:3000 |
| 后台 CMS | http://localhost:5173 |
| API | http://localhost:3001 |
默认管理员:admin / 123456
部署思路
生产环境可按域名拆分:
yourdomain.com → Next.js(Vercel / PM2)
admin.yourdomain.com → Vue 静态文件(Nginx)
api.yourdomain.com → Express(PM2)+ MySQL
三端通过环境变量指向彼此,互不耦合。详细步骤见项目 docs/deployment.md。
和同类方案的定位
开源社区里也有类似拆法(如 iizyd/express-blog、Lrunlin/web_blog)。RR Blog 的定位是:
- 工程干净:三端职责清晰,无过度抽象
- 模式可延续:与 backendnode / vue admin 同一套开发习惯
- 文档完整:中文 docs 覆盖从建表到部署
- 功能够用:评论审核、RSS、浏览量、ISR 刷新,个人博客 v1 所需齐全
适合已有 Vue + Express 经验、想快速搭一套可维护博客的开发者。
后续方向
- Markdown 双轨编辑(富文本 + MD 源码)
- 友链、关于页、系列文章导航
- 图片上传接入 OSS
- Monorepo + 共享 TypeScript 类型
写在最后
这套方案解决的是工程问题:前台 SEO 要好、后台要好写、代码要好维护。
技术栈可以替换,但「按场景拆分、API 统一收口、复用已有模式」这条思路可以复用到别的内容型项目里。
如果你也在做类似的全栈博客,欢迎交流架构细节。
技术栈:Next.js 15 · Vue 3 · Express · MySQL 8 · Tailwind CSS · Element Plus · WangEditor