编程
从博客到内容系统:一次长期重构的技术演进记录
项目从简单个人博客演进为轻量CMS,经历静态页面、Vue SPA、Nuxt SSR、自研SSR等阶段,最终采用mono+stereo分层架构,清晰分离内容管理与前台展示,解决性能、SEO和长期维护问题
项目从简单个人博客演进为轻量CMS,经历静态页面、Vue SPA、Nuxt SSR、自研SSR等阶段,最终采用mono+stereo分层架构,清晰分离内容管理与前台展示,解决性能、SEO和长期维护问题
这个项目最早并不是一个完整的内容系统,只是一个很简单的个人博客。最初的目标也很直接:能把文章展示出来,能上线,能被访问。那时候技术选型没有复杂考虑,HTML、CSS、JavaScript 直接写页面,结构简单,开发也快。这个阶段最大的价值不是技术多高级,而是把最核心的业务流程跑通了:内容能展示,页面能打开,访问链路能走完。
后来内容逐渐变多,页面也不再只是几个静态模块,原生写法开始暴露问题。组件复用差、状态管理混乱、页面交互维护成本变高。于是项目进入 Vue SPA 阶段,用 Vue 做组件化,把页面拆成可维护的模块。SPA 带来了更顺滑的页面切换体验,也让前端工程化真正介入项目:模块化、构建、资源管理、开发规范,这些能力让项目从“能写出来”变成“能持续迭代”。
但 SPA 很快遇到另一个问题:首屏和 SEO。博客这类内容站,搜索引擎收录和首屏展示非常重要。纯前端渲染需要先下载 JS,再请求数据,再渲染页面,对用户和爬虫都不够友好。于是项目开始引入 SSR 思路,先用 Nuxt 解决服务端渲染,让服务端直接返回可读的 HTML。这个变化对内容站很关键,首屏更快,页面内容也更容易被搜索引擎理解。
后面又进一步从 Nuxt 迁移到自研 SSR 脚手架。原因也很简单:项目需求不复杂,但对渲染细节和性能控制要求越来越高。全功能框架当然省心,但有些能力用不上,有些细节不好完全掌控。自研 SSR 脚手架让渲染流程更透明,也方便针对具体问题做处理。比如后来遇到超长文章导致 renderToString() CPU 阻塞,线上出现 504 Gateway Time-out。后台接口和缓存都正常,问题卡在主站 SSR 渲染。最后把:
ctx.body = await renderer.renderToString(context);改成:
const renderStream = renderer.renderToStream(context);
ctx.body = renderStream;页面就恢复正常。这个问题很典型:不是数据库慢,也不是接口慢,而是 SSR 在处理超长字符串时被同步渲染拖住了。换成流式输出后,渲染压力被拆开,响应不再被一次性大字符串卡死。
基础设施也经历过几轮调整。最早服务器在海外,访问速度和稳定性都不够理想。后来迁到阿里云 ECS,配合 HTTPS 和 DNS 解析优化,国内访问速度明显更稳。对象存储也从海外服务迁移到国内可控的方案,再到后来结合又拍云、CDN 做静态资源分发。图片、CSS、JS 这些资源从离用户更近的节点返回,页面加载速度有明显提升。
性能优化不是单点做的,而是一层一层往下拆。静态资源走 CDN,对象存储承接图片和媒体;动态数据则用 Redis 做高频读取缓存,减少 MySQL 压力。博客内容读取又引入过 MongoDB 缓存:MySQL 负责管理和写入,更新时同步到 MongoDB,前台读取走更适合查询的缓存层。这个方案不是为了追求架构复杂,而是在当时的数据访问模式下,让首页和文章页更快返回。
后台系统也逐渐从“能发布内容”变成“能管理内容”。文章分类、年份筛选、置顶、封面选择、批量上传图片和视频,这些功能都是在实际使用中慢慢长出来的。项目也增加了个人记录模块,类似树洞和微博之间的形态,用来记录碎念、灵感和短内容。它不像正式文章那样需要完整结构,但更适合高频记录。
留言板后来接入 GitHub OAuth2。这个功能的重点不是登录本身,而是让互动从匿名留言变成可识别身份的交流。用户用 GitHub 登录后留言,前端生成 redirectUri 和随机 state,回调时校验 state,再调用后端完成 code 换取登录态。登录成功后展示头像、昵称,博主侧基于 token 展示回复和删除能力。这个阶段让项目从单向内容发布,开始有了一点社区互动的味道。
再后面,项目页也做过重构。原来的 home 页面改成 projects,项目标题、外链图标、GitHub 图标、认证提示、移动端布局都重新处理了一遍。这个阶段更偏 UI 和体验,目标是让项目展示不只是“列出来”,而是读起来顺、点起来清楚、移动端也不别扭。
这些都是旧架构一路走过来的过程。它解决了很多真实问题,也积累了很多经验:SSR、缓存、对象存储、CDN、OAuth、CI/CD、PM2 运维、日志切割、媒体管理、后台编辑。但随着功能越来越多,旧架构也开始变重。很多能力是逐步叠上去的,边界没有一开始就划清楚。前台、后台、缓存、接口、内容模型之间的关系越来越复杂,后续维护成本也越来越高。
所以现在这个项目换成了新的架构。
新架构拆成两个核心部分:mono 和 stereo。
mono 是内容管理和服务端能力的主仓库,包含后台管理端和 API 服务端。后台使用 Vue 3、Vite、Pinia、Arco Design Vue,主要负责文章、动态、知识库、媒体库、评论、用户、权限、系统配置等管理能力。服务端使用 Koa、Prisma、MySQL,提供后台 API 和公开 Web API。MySQL 作为主数据源,Redis 用于缓存和会话相关能力,对象存储用于媒体资源。
stereo 是独立的 Astro SSR 前台。它不直接连数据库,而是消费 mono 暴露出来的 /api/v1/web/* 接口。浏览器访问页面时,Astro 在服务端请求 Web API,把文章、动态、知识库、个人介绍等内容渲染成 HTML 再返回。这样前台保持 SSR 的首屏和 SEO 优势,同时和后台系统解耦。
这次新架构最重要的变化,是边界更清楚。
旧系统里很多功能是在一个项目里不断叠加,新系统则把职责拆开:
mono:内容生产、权限、审核、配置、媒体、API
stereo:内容消费、SSR、前台展示、搜索入口、详情页体验
前台不再关心后台内部怎么存数据,也不直接依赖管理端逻辑。后台只需要通过稳定的 Web API 输出前台需要的数据。这个边界让后续维护更舒服,也方便前台单独优化性能和体验。
内容模型也更完整了。现在不仅有文章,还有动态、知识库、个人介绍、音乐目录、评论、客户端用户等模块。文章适合长内容,动态适合短记录,知识库适合沉淀问题和解决方案。不同内容类型用不同结构,不再把所有东西都塞进“博客文章”里。
后台管理也更系统化。权限、角色、菜单、媒体审核、评论审核、客户端用户、配置项都被独立出来。媒体库支持图片、视频上传和审核,文章和动态都可以绑定媒体。最近又加入了上传图片自动转 WebP:用户上传 png/jpg/jpeg 后,服务端用 sharp 转成 WebP,再写入 OSS。数据库里保留原始文件名,但实际存储的文件名、MIME 和大小都按 WebP 结果记录。这样前台图片体积会明显下降,也更适合 CDN 分发。
前台 stereo 这边则更注重首屏、阅读和内容消费。首页是文章流和动态流,支持服务端渲染、无限加载、滚动状态恢复。文章和动态详情页都有评论、访问统计、分享海报。分享海报也做了权限控制,只有具备对应客户端能力的用户能看到入口。文章海报不再依赖固定远程 banner 图,改成了本地 canvas 绘制的抽象图形,减少外部资源依赖,也更符合当前站点风格。
置顶功能也按新的思路做了。不是简单给内容加一个 isPinned 然后无限堆在列表顶部,而是后端把置顶内容和普通列表分开返回:
{
"pinned": [],
"list": [],
"page": 1,
"pageSize": 10,
"total": 20,
"pinnedTotal": 100
}前台只展示最多 5 条置顶内容,且用紧凑单行样式,避免占用过多首屏高度。无限加载只加载普通列表,避免置顶内容重复出现。这个设计比旧式“置顶就是排序靠前”更清晰,也更适合后续扩展。
缓存策略也更克制。旧系统里曾经为了性能引入 Redis、MongoDB 双写缓存。现在新架构仍然保留 Redis 能力,但更强调“哪里慢再缓存哪里”。公开 Web API 可以按内容类型、详情页、首页概览做缓存,后台更新内容后清理相关缓存。这样比一开始就引入多套读模型更容易维护,也更少出一致性问题。
CI/CD 和运维能力也延续下来。项目仍然通过 GitHub Actions 做自动化发布,线上用 PM2 管理服务。过去遇到过 PM2 日志暴涨到几十 GB,导致磁盘和 I/O 出问题,所以现在运维上会更早考虑日志切割、保留天数和压缩策略。这类问题不写进功能列表里,但它决定了服务能不能长期稳定运行。
如果说旧架构是一条不断探索的路,那么新架构更像是把之前踩过的坑重新整理了一遍。
旧架构证明了这些事情是必要的:
内容站需要 SSR
图片和静态资源必须走 CDN
后台管理不能只靠数据库手改
缓存能明显提升体验,但不能滥用
生产日志和部署流程必须可控
长内容渲染要考虑服务端压力
新架构则把这些经验落成了更清晰的工程结构:
用 mono 管内容和 API
用 stereo 做前台 SSR
用 MySQL 做主数据
用 Redis 做缓存
用对象存储和 CDN 承接媒体资源
用后台权限和审核保证内容生产可控
用 Web API 隔离前后台边界
现在这个项目已经不只是一个博客了,更像一个围绕个人内容生产和展示的轻量 CMS。它有文章,有动态,有知识库,有媒体管理,有评论,有客户端权限,也有前台 SSR 展示。它不是为了堆技术,而是一步步从实际问题里长出来的。
从最早的 HTML 页面,到 Vue SPA,再到 SSR、自研 SSR、CI/CD、CDN、缓存、后台系统,再到现在的 mono + stereo 分层架构,这条线其实很清楚:每次技术变化都不是为了换个新名字,而是为了解决当时最卡的问题。
最开始解决“能不能上线”。
后来解决“能不能维护”。
再后来解决“能不能被搜索引擎看到”。
继续往后解决“能不能更快”。
现在解决的是“能不能长期演进”。
这也是现在新架构最大的价值。它不是把旧项目推倒重来,而是把过去几年积累的经验重新整理成更清晰、更稳定、更容易继续扩展的系统。
技术演进路线图
版本1 : HTML / CSS / JavaScript
: 快速上线
: 验证博客展示和基础业务流程
版本2 : Vue SPA
: 组件化开发
: 页面切换更顺滑
: 前端工程化开始介入
版本3 : Nuxt SSR
: 解决首屏白屏
: 改善 SEO
: 服务端返回完整 HTML
版本4 : 自研 Vue SSR
: 控制渲染细节
: 降低框架冗余
: 用 renderToStream 解决超长文章 504
版本5 : GitHub Actions
: 自动构建部署
: 减少人工发布错误
: 发布流程稳定化
版本6 : 阿里云 ECS / HTTPS / DNS
: 从海外迁回国内
: 提升访问速度
: 基础设施稳定化
版本7 : CDN / OSS / Redis / MongoDB
: 静态资源加速
: 动态数据缓存
: 首屏性能优化
版本8 : UI / UX 优化
: 阅读体验增强
: 目录、排版、动画细节完善
版本9 : 个人记录模块
: 支持碎念和灵感记录
: 内容形态从长文扩展到短内容
版本10 : 懒加载 / 组件封装 / 运维优化
: IntersectionObserver 图片懒加载
: Toast / Dialog / Popup 组件化
: PM2 日志切割解决磁盘和 I/O 风险
版本11 : GitHub OAuth 留言板
: 访客身份可识别
: 留言互动更可信
版本12 : 项目展示页重构
: projects 页面升级
: GitHub 图标、外链图标、响应式体验完善
当前版本 : Mono + Stereo
: 后台和前台拆分
: Koa + Prisma + MySQL 提供内容服务
: Astro SSR 消费 Web API
: Redis / OSS / CDN 继续服务性能优化
新旧架构对比
旧架构更像一条持续叠加的路线:先能跑,再补 SSR,再补缓存,再补后台,再补运维。
新架构开始强调边界:mono 管内容生产和 API,stereo 管前台展示和 SSR。
旧系统解决的是“这个博客怎么变快、怎么变稳”。
新系统解决的是“这个内容系统怎么长期演进”。
暂无评论
确认操作
请再次确认
确认评论邮箱
邀请制评论