大四的课表空得让人有些不知所措。广州的十一月,天气依然带着些许闷热,宿舍楼里偶尔传来外卖小哥的脚步声和隔壁打游戏的键盘敲击声。作为软件工程专业的学生,当学分已经修满,绩点定格在4.02,两次国家奖学金的证书也被平整地收进抽屉后,面对即将到来的毕业和不可预知的未来,人反而会陷入一种短暂的失重感。
在这种大段的空白时间里,我决定把之前一直想做但没做完的事情做个了结——搭一个真正属于自己的博客。
这个博客从最初的想法到最终上线,前前后后折腾了不少时间。我没有选择一键部署的傻瓜方案,而是像拼乐高一样,把各个组件一点点拼凑起来。在这里记录一下整个过程中的技术选型和踩坑经历,算是给这段时间的一个交代。
关于技术选型,其实是一个不断推翻自己的过程。
最开始接触博客是在大二,那时候大家都在用 Hexo 或者 Hugo。静态博客的好处很明显,生成速度极快,丢到 GitHub Pages 上就能跑,几乎没有维护成本。但我用了几个月就放弃了。每次有了一点零碎的想法,或者只是想发一条类似朋友圈的短动态,都需要经历“打开终端 - 新建文件 - 编写 Markdown - 本地生成 - 推送代码”这样一套繁琐的流程。这种割裂感会慢慢消耗掉表达的欲望。动态功能的缺失,让静态博客像是一个只读的橱窗,而不是一个可以随时互动的客厅。
后来也考虑过 WordPress。它的生态确实无可挑剔,几乎你想要的所有功能都能找到对应的插件。但我始终对它提不起兴趣。一方面是 PHP 技术栈对我这个更习惯现代前端和 Node.js 的人来说,显得有些陌生和遥远;另一方面,WordPress 实在太臃肿了,后台错综复杂的菜单让人看着心烦,我只是想要一个安静写字的地方。
在 GitHub 上漫无目的地翻找时,我看到了 Mix Space。配合 Shiroi 这个前端主题,那种干净、克制又注重细节的 UI 风格瞬间击中了我。它采用了现代的前后端分离架构,技术栈是我熟悉的 React 和 NestJS。虽然部署起来比静态博客复杂得多,但这种掌控每一行代码运行轨迹的感觉,正是我所需要的。
决定了方案,接下来就是规划部署架构。
我把前端和后端拆开来放。前端的 Shiroi 部署在 Vercel 上。Vercel 的体验依然是那么优雅,绑定 GitHub 仓库后,每次 push 都会自动触发构建,再配合它全球分布的 CDN,静态资源的加载速度得到了很好的保证。
后端的 Mix Space Core 和数据库则跑在我自己买的一台 VPS 上。这台服务器的配置不高,但在夜深人静的时候,通过 SSH 连上去,看着黑色终端里跳动的光标,会有一种待在自己房间里的踏实感。
整个架构的数据流向其实很清晰:访客通过浏览器访问 Vercel 托管的前端页面,页面需要数据时,会向我的服务器发起 API 请求。请求首先到达 Nginx,Nginx 作为反向代理,将流量转发给背后由 PM2 守护的 Node.js 进程,最后 Node.js 再去 MongoDB 里读取或写入数据。
画在纸上只是几个简单的方框和箭头,但在实际落地的过程中,坑是一个接一个地踩。
最先遇到的是老生常谈的 CORS 跨域问题。
前端托管在 Vercel,绑定了我自己的域名 km.caiths.com,而后端 API 则跑在 kami.caiths.com。当我在前端页面尝试登录或者获取文章列表时,浏览器的控制台毫不留情地弹出了一大片红色的错误提示,请求被同源策略拦截了。
最暴力的解决办法是在后端的响应头里加上 Access-Control-Allow-Origin: *,允许所有域名跨域。但这种做法在个人的正式项目里显得太不严谨。我希望只有我自己的几个子域名能够访问 API。
于是我打开服务器上的 Nginx 配置文件。在 vim 编辑器里,我利用 Nginx 的 map 指令写了一段动态匹配的逻辑:
map $http_origin $cors_origin {
~^https://(km|blog|kami)\.caiths\.com$ $http_origin;
default "";
}
add_header Access-Control-Allow-Origin $cors_origin;
这段配置的意思是,当请求头里的 origin 匹配我指定的几个子域名时,就把这个域名赋值给 $cors_origin 变量,并在响应头里返回。保存,执行 nginx -s reload。切回浏览器,刷新页面,看着 Network 面板里原本红色的请求一个个变成绿色的 200,那种瞬间的通畅感,是写代码独有的反馈。
如果说跨域只是一个小麻烦,那 MongoDB 的安全问题则给我上了一堂深刻的实战课。
刚开始部署数据库的时候,为了图省事,我直接用了默认配置,也没有设置访问密码,甚至让 MongoDB 监听了 0.0.0.0,这意味着公网上的任何机器都能连接到我的数据库。当时心里存着侥幸,觉得一个刚上线没几天的个人博客,谁会来攻击呢。
现实很快给出了答案。几天后的一个早上,我醒来习惯性地打开博客后台,发现文章列表空空如也。心里一沉,赶紧连上服务器查看数据库。原本的数据集合全都不见了,只剩下一个名为 READ_ME_TO_RECOVER 的集合。点开一看,是一封勒索信,要求我支付零点几个比特币来赎回数据。
看着屏幕上的勒索信息,我没有太多的愤怒,更多的是对自己粗心大意的无奈。好在博客刚建,没写几篇文章,本地也有 Markdown 备份。我索性删掉了整个数据库容器,重新开始。这一次,我老老实实地把 MongoDB 的监听地址绑定到了 127.0.0.1,意味着它只接受来自服务器内部的连接。同时开启了 auth 认证,设置了复杂的账号密码。书本上学过的网络安全原则,只有在自己真正痛失数据后,才会形成肌肉记忆。
最后一个小插曲关于 Node.js 的版本。
前段时间 Mix Space 发布了 v10 版本的更新。我习惯性地拉取最新代码准备重启服务,却发现控制台报错,提示当前环境不满足要求。仔细看了更新日志才发现,新版本使用了最新的语法特性,要求 Node.js 版本必须在 22 以上,而我服务器上跑的还是 20。
在 Linux 下管理 Node 版本,nvm 是最好的工具。敲下 nvm install 22,看着终端里进度条慢慢推进,下载、解压、更新环境变量。随后执行 nvm use 22 和 nvm alias default 22。再次启动 PM2,看着应用状态从 erring 变成绿色的 online,一切又恢复了平静。
现在,这个博客已经安安静静地运行在互联网的某个角落里了。
回过头来看,搭建博客的过程本身,其实就是最好的学习。在广州商学院这四年的科班学习,给了我理论的框架,但很多东西只有亲手摸过才会真正懂。从去域名注册商那里配置 DNS 解析,到申请并自动续期 SSL 证书;从手写 Nginx 反向代理规则,到用 PM2 管理进程的生命周期。每解决一个报错,每敲下一次回车,都在不知不觉中加深着我对 Web 全栈的理解。
在这个信息过载、算法推荐泛滥的时代,拥有一个完全属于自己的数字空间变得越来越奢侈。博客不需要迎合别人的口味,也不需要关心阅读量和点赞数。它只是静静地待在那里,记录着我在某个深夜写下的代码,或者某个午后脑海中闪过的思绪。
电脑风扇的声音渐渐微弱下去,博客的后台面板显示着一切正常。我合上电脑,明天又是新的一天。