个人博客不一定要单栈:Next.js 做展示、Vue 做 CMS、Express 做 API 的混合架构实践

152 阅读7分钟

前言:为什么不用「一个框架搞定一切」?

做个人博客,常见路线有三条:

  • 静态站点生成器(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

这样拆的四个好处

  1. SEO 有保障:App Router + generateMetadata + sitemap + RSS,公开页走 ISR
  2. 后台开发效率高:Element Plus 表格/表单/弹窗开箱即用,RBAC 菜单后端动态下发
  3. API 可复用:同一套服务供前台、后台、未来的小程序或 RSS 阅读器
  4. 部署灵活:三端可同机跑,也可拆到 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 SSRISR/SEO 生态成熟
Vue 后台React Admin、Strapi与现有 Vue 项目模式一致
Express APINestJS轻量,RBAC/上传模式现成
WangEditorMarkdown后台 WYSIWYG,前台直渲染 HTML
MySQLMongoDB博客 + 权限关系型建模更自然

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
后台 CMShttp://localhost:5173
APIhttp://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 统一收口、复用已有模式」这条思路可以复用到别的内容型项目里。

如果你也在做类似的全栈博客,欢迎交流架构细节。


项目地址:github.com/zengyirong/…

技术栈:Next.js 15 · Vue 3 · Express · MySQL 8 · Tailwind CSS · Element Plus · WangEditor