为什么不用现成的博客系统?

市面上的博客方案大致分两类:一类是 WordPress、Halo 这样的动态博客,功能强大但后台和前台是两套界面,风格割裂;另一类是 Hexo、Hugo 这样的静态博客,轻量快速但交互体验差,每次写文章都要在本地编辑、生成、部署。

我想要的很简单:在博客页面上直接写文章。登录后,文章旁边出现「编辑」「删除」按钮,点开就是一个和博客风格完全一致的编辑器。不需要单独的后台页面,不需要切换风格,所见即所得。

于是决定自己动手,做一个前后台一体化的博客系统。

设计思路

前后台一体化

核心想法是:编辑功能直接嵌入博客前台。未登录时,访客看到的是纯粹的阅读界面;登录后,每篇文章旁边出现管理按钮,点击即可在当前页面内弹出编辑器。编辑器的样式和博客主题完全一致——编辑时看到什么效果,发布后就是什么效果。

这样做的好处:

  • 没有风格割裂感,编辑和阅读体验统一

  • 减少页面跳转,写作流程更顺畅

  • 不需要维护两套界面和样式

权限管理

只有经过授权的管理员账号才能编辑。登录入口是隐蔽的 URL 路径,不对外公开。登录后通过 JWT Cookie 维持会话,7 天有效。其他人只能查看文章,看不到任何管理入口。

风格保留

整体视觉风格保留「古籍书页 / 编辑杂志」的设计语言:暖灰背景、古铜金点睛、紧凑信息密度。这个风格是项目的核心审美,博客只是换了一种交互方式,视觉基因不变。

视觉样式

博客的视觉设计围绕三个关键词展开:

古籍书页

背景色采用暖灰(#f5f1ea),模拟旧书纸张的质感。正文使用衬线字体增加阅读沉浸感,标题用无衬线字体保证辨识度。整体色调偏暖偏沉,避免高饱和度的色彩刺激。

古铜金点睛

强调色用古铜金(#c8a86e),出现在按钮、链接 hover、分割线、标签等细节处。这个颜色既有金属质感又不张扬,和暖灰背景搭配出沉稳的气质。

编辑杂志排版

文章列表采用卡片式布局,每张卡片包含标题、摘要、分类标签和发布时间。首页使用 scroll-snap 实现丝滑的全屏滚动体验。文章详情页有清晰的层级结构:标题、元信息、正文、代码块都有明确的视觉区分。

技术实现

技术栈

整体技术选型如下:

前端:EJS 模板 + 原生 CSS/JS(无框架)
编辑器:Tiptap(ProseMirror 内核的 WYSIWYG 编辑器)
后端:Node.js + Express
数据库:SQLite(better-sqlite3)
认证:JWT + HttpOnly Cookie
进程管理:PM2
Web 服务器:Nginx(反向代理)

为什么选 SQLite?

个人博客数据量小,SQLite 是最合适的选择:零配置、文件型数据库、不需要单独运行数据库服务、备份只需复制文件。better-sqlite3 是同步 API,代码写起来简单直接,性能也完全够用。开启 WAL 模式后并发读写也没有问题。

为什么选 Tiptap?

Tiptap 是基于 ProseMirror 的富文本编辑器,它的核心优势是:输出干净的 HTML,可以自定义样式完全融入页面主题。不像 Quill 或 CKEditor 那样自带一套难以覆盖的样式。编辑器里看到的效果和发布后的效果完全一致,真正做到了「所见即所得」。

项目结构

博客/
├── app.js              # Express 主入口
├── db.js               # SQLite 数据库操作
├── .env                # 环境变量配置
├── middleware/
│   └── auth.js         # JWT 认证中间件
├── routes/
│   ├── index.js        # 首页路由
│   ├── post.js         # 文章详情路由
│   ├── auth.js         # 登录/登出
│   └── api.js          # 文章 CRUD API
├── views/              # EJS 模板
│   ├── partials/       # 公共组件
│   ├── index.ejs       # 首页
│   ├── post.ejs        # 文章页
│   └── login.ejs       # 登录页
├── public/
│   ├── css/            # 样式文件
│   ├── js/             # 前端脚本
│   └── img/uploads/    # 上传的图片
└── data/
    └── blog.db         # SQLite 数据库文件

关键功能实现

就地编辑:登录后,每篇文章卡片上出现「编辑」按钮。点击后在当前页面弹出全屏编辑器覆盖层,加载 Tiptap 编辑器和文章元数据(标题、分类、标签、状态)。编辑器使用和博客相同的 CSS,保证编辑预览效果和发布效果一致。

自动保存草稿:编辑过程中,每次内容变化触发防抖自动保存到浏览器 localStorage。新建文章时自动恢复上次未保存的草稿,编辑已有文章时如果检测到本地草稿会提示是否恢复。这样即使浏览器崩溃或误关闭,内容也不会丢失。

图片上传:在编辑器中 Ctrl+V 粘贴图片,自动上传到服务器并插入到文章中。使用 multer 处理文件上传,图片存储在 public/img/uploads/ 目录下。

代码块复制:文章中的代码块右上角自动出现「复制」按钮,点击一键复制代码内容。使用 MutationObserver 监听 DOM 变化,动态为新增的代码块添加复制按钮,并做了防抖和防重入处理避免性能问题。

隐蔽登录入口:登录页面不在导航中暴露,只有知道特定 URL 路径的人才能访问。路径格式不规律,有效防止扫描和猜测。

部署架构

用户浏览器
    ↓ HTTPS
Nginx (443)           ← 反向代理 + SSL 终止 + 静态资源缓存
    ↓ HTTP (127.0.0.1:3000)
Node.js/Express       ← 博客应用
    ↓
SQLite (blog.db)      ← 文件型数据库

PM2 管理进程           ← 崩溃自动重启 + 开机自启

Nginx 负责 HTTPS 终止、gzip 压缩、静态资源缓存,然后通过反向代理把请求转发给 Node.js 应用。PM2 确保应用进程稳定运行,崩溃自动重启,服务器重启后自动启动。

从 Hexo 到自建博客

这个博客的前身是一个 Hexo 静态博客。Hexo 的优点是简单、快速、适合纯写作,但它的交互方式不适合我:写文章要去后台、生成静态文件、同步到服务器,流程太长。而且 Hexo 的后台管理界面和博客前台的样式完全不同,切换起来有割裂感。

自建博客虽然投入了更多开发时间,但换来了完全可控的体验:编辑即发布、风格统一、功能按需定制。旧 Hexo 博客的文件已经完整备份,随时可以恢复。

总结

这个博客系统的核心设计理念是前后台一体化——编辑功能融入阅读界面,写作和发布在同一空间完成。技术上用 Node.js + Express + SQLite 保持轻量,用 Tiptap 实现所见即所得的编辑体验,用 PM2 + Nginx 保证生产环境的稳定性。

它不是最强大的博客系统,但它恰好是我想要的样子。