外观
Sumjess 技术博客工程化改造复盘
约 2897 字大约 10 分钟
VuePress后台系统静态站点媒体库
2026-06-11
这篇文章记录的是 www.sumjess.com 最近一次比较完整的工程化改造。
它原本是一个典型的静态技术博客:文章写在 Markdown 里,VuePress 构建成静态文件,再由 Nginx 对外提供访问。这个模式很稳,也很快,但缺点也明显:想改一篇文章、加一张图、调一个导航,都要回到服务器或源码仓库里处理。时间久了,发布流程、图片资源、备份、访问安全和后台编辑就会变成一堆散落的小问题。
这次改造没有把网站推倒重做成动态站,而是选择了一个更轻的方向:
前台继续静态化,后台只负责编辑、媒体、发布、统计和安全这些必要的动态能力。

为什么没有直接改成动态站
一开始也考虑过:既然文章编辑麻烦,那是不是应该直接换成动态博客?比如数据库存文章,后台在线编辑,保存后马上可见。
但仔细拆开以后发现,真正的痛点不是“静态页面本身”,而是“发布和维护链路不够顺”。
静态站点的优势仍然很适合技术博客:
- 访问速度快,页面可以直接由 Nginx 托管。
- 运行时依赖少,数据库、缓存、后台服务故障不会影响已有文章阅读。
- SEO 和长期归档友好,文章天然是文件和 Git 历史。
- 出问题时回滚简单,静态目录可以直接恢复。
所以最终的判断是:不要为了“编辑方便”牺牲静态站的稳定性。更好的方案是把编辑、图片、发布、安全这些动态能力加在旁边,让它们服务静态站,而不是替代静态站。
这套系统现在由哪些部分组成
现在整个项目大概可以分成六块:
| 模块 | 作用 |
|---|---|
| VuePress / Plume 前台 | 负责公开文章、首页、导航、搜索和评论挂载 |
| React + Express 后台 | 负责文章编辑、媒体上传、导航配置、发布触发和设置 |
| BlockNote 编辑器 | 负责块编辑体验,内容可导出回 Markdown |
| 媒体库 | 负责图片和文件的长期托管 |
| 发布脚本 | 负责构建、备份、同步、校验和推送源码 |
| 运行时数据 | 负责浏览量、点赞、评论提醒、安全日志、审计记录等 |
这个结构有一个重要原则:源码、线上静态文件、后台运行数据分开。
源码继续进 Git;线上静态文件由发布脚本生成;后台运行数据不进 Git,避免把密码、Token、审计日志、临时状态混到源码里。
后台:给静态站加一个轻量控制台
后台不是 CMS,也不是完整的内容平台,它更像一个“博客维护工作台”。
它主要做这些事情:
- 浏览文章列表。
- 搜索标题、路径和永久链接。
- 用 BlockNote 编辑文章内容。
- 把编辑内容保存回 Markdown。
- 导入外部 Markdown 文件。
- 管理媒体库上传。
- 编辑导航配置。
- 查看评论提醒。
- 触发发布。
- 查看备份、Git 状态、部署信息和定时任务状态。
编辑器这块使用的是 BlockNote。它底层属于 ProseMirror / TipTap 系生态,但项目里直接使用的是 BlockNote 的 blocks JSON。这样做的好处是编辑体验更接近现代块编辑器;同时在发布时仍然可以导出成 Markdown,让 VuePress 继续按原来的静态方式构建。
也就是说,后台只是让“写文章”这件事更舒服,并没有改变前台文章的最终形态。
图片:从本地散落文件迁移到媒体库
图片资源是这次改造里很关键的一块。
旧模式下,很多图片直接放在站点 public 目录里。时间久了会出现几个问题:
- 哪些图片还被文章引用,不清楚。
- 原图体积大,静态目录越来越重。
- 本地图片和文章路径强绑定,迁移不方便。
- 用户编辑文章时上传了图片但没保存,容易产生孤儿文件。
新的媒体流程是这样的:

后台上传图片时,浏览器不会拿到媒体库 Token。文件先发到博客后台,再由后台服务端代理上传到媒体库。图片会先在内存里通过 sharp 转成 WebP,再上传到媒体库。原始图片不落盘,避免云服务器慢慢堆积临时文件。
还有一个细节很重要:如果用户在编辑文章时上传了图片,但最后没有保存文章,或者直接关掉浏览器,这张图不应该永久留下。现在的思路是把这类图片先标记为 pending,只有文章保存后才确认使用;超时或主动放弃时就清理掉。
历史文章里的本地 PNG/JPG/JPEG 也做过一次迁移:批量扫描 Markdown 引用,分批转换 WebP,上传到媒体库,然后把文章里的本地图片路径替换成媒体 URL。上传时控制批量大小和间隔,避免对媒体接口造成压力。
发布:一条可重复、可验证的生产路径
发布流程也从“人记得怎么操作”变成了“脚本负责兜底”。

正常发布会走同一条链路:
pnpm run publish这条链路大致包含:
- 检查工作区是否干净。
- 检查当前分支和远程仓库状态。
- 构建 VuePress 静态站点。
- 更新部署信息。
- 给当前线上静态目录做预发布备份。
- 用
rsync --delete同步新构建结果。 - 检查首页、关键页面和
deploy-info.json。 - 把同一份源码修订推送到私有 Git 远程。
这里最有价值的不是某一条命令,而是“发布状态变得可见”。首页和页脚会读取 deploy-info.json,显示最近部署时间和 build id。这样部署完以后,不用猜“到底是不是新版本”,打开网站就能看到。
备份也做了收敛。每次部署前会保留线上静态目录的预发布快照,但只保留最近几份。它们是回滚用的,不是源码备份;源码备份仍然依赖 Git 远程和 NAS 层面的快照或备份策略。
安全:小后台也要有边界
后台一旦能改文章、发图片、触发发布,就不能只靠“地址没人知道”。
这次安全设计没有追求很重的企业级权限系统,而是先把个人站最关键的边界补上:

目前后台侧包含:
- 登录认证。
- CSRF 校验。
- 后台入口通过 Nginx 反向代理,只监听本机端口。
- 媒体 Token 只存在服务端配置中,不传给浏览器。
- IP 白名单 / 黑名单。
- 可选的中国 IP 访问限制。
- 脚本和爬虫类请求拦截。
- 高频访问临时拉黑。
- 访问日志展示,并区分前台 / 后台访问。
- 最近访问里显示 IP 国家归属地。
同时,公开侧的动态 API 尽量保持窄边界。例如浏览量、点赞这类能力放在轻量接口里,不要求整站动态化;并且这类运行时数据不存原始 IP,只存去标识化后的短标记,用于基本去重。
首页和运行时数据
前台首页也做了几块调整。
现在首页不再只是静态介绍,而是有几类轻量数据:
- 全平台统计:总访问量、粉丝/用户、原创文章、收藏等。
- 本站数据:本站阅读、文章点赞、有互动文章等。
- AI 使用情况:可在后台配置不同平台、金额、Token 用量和套餐信息。
- 我的一点点:可配置的兴趣和技术标签滚动展示。
- 最近写的文章:展示最新文章标题和时间,点击跳转到文章。
这些数据的来源并不完全一样:有些来自 stats.json,有些来自后台运行时 JSON,有些来自文章列表生成结果。它们的共同点是:能静态化的尽量静态化,需要动态更新的才走小接口。
这个分层非常适合个人技术站。它让首页更像一个持续更新的技术档案,而不是每次都要手动改首页 Markdown。
评论提醒:不接管,但要看得见
评论系统使用 Artalk。后台没有重做评论审核系统,也没有替代 Artalk 原来的管理能力,只做了一层“提醒和入口”:
- 读取最近评论。
- 显示未读数量。
- 标记已读。
- 保留 Artalk 管理侧作为正式评论配置和管理入口。
这也是整套改造里很典型的思路:不重复造一个完整系统,只补自己日常维护中最需要的那一小层。
我觉得最值得复用的经验
这次改造最大的收获,不是某个功能做出来了,而是项目边界清楚了。
第一,静态站不等于低级。
如果内容是长期阅读为主,静态前台依然是非常好的底座。真正要补的是编辑、发布、媒体和观测。
第二,后台越小越好。
个人站后台不应该变成另一个复杂系统。能复用原来的 Git、Markdown、VuePress、Nginx,就不要过早引入数据库和完整 CMS。
第三,发布必须可验证。
构建成功不等于发布成功,文件同步成功也不等于公网已经是新版本。deploy-info.json、关键 URL 检查、首页可见 build id,这些小东西非常实用。
第四,图片资源必须有生命周期。
上传、转换、引用、保存、丢弃、清理,任何一步想不清楚,最后都会变成垃圾文件和坏链。
第五,安全不是一个开关。
登录、CSRF、Token 不下发、访问频率、IP 规则、审计日志、公开 API 边界,这些组合起来才像一个能长期运行的后台。
后续可以继续做什么
这套系统现在已经能覆盖日常写作和发布,但后面还可以继续优化:
- 后台编辑器的 Markdown 还原质量继续打磨。
- 媒体库增加更完整的引用关系和孤儿资源巡检。
- 发布记录做成更清晰的时间线。
- 安全页增加更细的访问趋势统计。
- 评论提醒增加文章维度聚合。
- 在不破坏静态前台的前提下,继续增强搜索和文章推荐。
我现在比较认可的方向仍然是:
保持前台静态,逐步把维护流程自动化、可视化、可回滚。
这样网站不会变重,但维护体验会一点点接近一个真正可长期运营的个人技术平台。
