[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-build-a-reliable-personal-site":3,"article-neighbors-build-a-reliable-personal-site":17,"article-related-build-a-reliable-personal-site":29},{"id":4,"slug":5,"title":6,"excerpt":7,"category":8,"tags":9,"content":13,"publishedAt":14,"readingMinutes":15,"published":16},1,"build-a-reliable-personal-site","个人网站工程架构与长期可维护设计","这个站从零搭起来，分享我用 Nuxt 加 Spring Boot 把个人项目做成敢一直改下去的经过。","工程实践",[10,11,12],"Nuxt","Spring Boot","部署","这个网站我从零开始搭，前前后后改了好几版。现在回头看，最想分享的其实不是\"用了什么框架\"，而是怎么让一个个人项目别在三个月后就变成一堆没法碰的烂代码。\n\n我见过太多个人站点：上线的时候很风光，过半年想加个功能，发现没人敢动了——到处是散落的逻辑、混乱的路由、全局样式互相打架。所以我从一开始就定了几个原则。\n\n### 前端为什么选 Nuxt\n\n技术选型上，我选了前后端分离：前端 Nuxt 做服务端渲染，后端 Spring Boot 分层，数据库用 PostgreSQL，中间 Nginx 反代。为什么选 Nuxt 而不是纯前端 SPA？因为内容站点最怕两件事：首屏白屏和搜索引擎不收录。\n\nSPA 的痛点是内容要靠 JavaScript 在浏览器里现渲染，首屏慢不说，爬虫还不一定能执行到那一步。Nuxt 的 SSR 能把页面在服务端渲染成完整的 HTML 再返回，首屏快、爬虫能抓到、SEO 友好。它还支持 SSG（静态生成），对不常变的页面可以提前生成静态文件，配合 CDN 就是又快又省。框架自带的约定式路由、布局插槽，帮我省掉了大量重复的页面结构代码。\n\n这里多说一句 SSR 的原理：页面在服务端先跑一遍组件，渲染出完整的 HTML 字符串返回浏览器，浏览器拿到手就是\"所见即所得\"的内容，然后再通过\"水合（hydration）\"把 JavaScript 事件绑定上去。这样既拿到了\"首屏即内容\"的速度，又保留了单页应用的交互能力。代价是会多一点服务端渲染的耗时——但对个人站来说，这个耗时完全可以接受。如果哪天流量大了，我还能把热门页面切成 SSG，进一步压缩渲染成本。\n\n### 后端分层的意义\n\n后端我坚持用经典的四层：Controller 只接 HTTP 请求和返回，Service 放业务规则，DAO 只写 SQL，Entity 描述数据库结构。谁越界谁负责，规则写清楚，加功能的时候就不会乱。接口统一返回 `{code, message, data}` 的结构，异常全部交给全局处理器，绝不让 Java 的堆栈直接抛给前端——不然前端写起来全是噩梦。\n\n这套分层看着像老生常谈，但它是让项目\"敢一直改下去\"的关键。因为每一层只干自己的事，改一个接口的返回格式，不用动业务逻辑；换一个数据库方言，不用动 Service 层。耦合度降下来，迭代速度才能上去。连接池用的是 HikariCP，性能好、配置简单，默认就能扛住个人站的并发。\n\n接口设计上我也定了几条约定：所有写操作都要校验参数（Bean Validation），所有查询都要做分页上限控制，所有返回都走统一的响应结构。这些约定看着琐碎，但它们是\"接口一致性\"的保证——前端不用为每个接口单独写适配，一次封装全局复用。\n\n### 数据库层的小约定\n\n数据库层我做了几件小事，但都很管用：所有内容表都有 `id`、`slug`、`created_at`、`updated_at` 和逻辑删除标记。逻辑删除不是偷懒，是防误删——哪天误操作了还能恢复。文章表再加上 `status`、`published_at`、`reading_minutes`，草稿和正式发布就能分清楚。\n\n`slug` 字段也值得单独说：它是文章的 URL 标识，用可读的英文短横线，而不是数字 ID。这样链接更友好、更好记，迁移域名时也不用担心 ID 变动导致链接失效。列表页和详情页的查询，都靠 `status + published_at` 的联合索引，性能在个人站这个量级绰绰有余。\n\n还有几个容易被忽略的细节：所有时间字段统一存 UTC，展示时再转本地时区，避免时区混乱；全文搜索我用的是 PostgreSQL 自带的 `tsvector` 全文检索，配合中文分词，个人站的内容量完全够用，不用额外引入 Elasticsearch 这种重型组件——这也符合\"匹配业务体量\"的选型原则。\n\n### 发布流程和回滚\n\n发布流程我也固定成了一条链：构建后端 → 备份旧版 → 上传新包 → 查健康接口 → 切前端 → 验证页面。发布失败必须能一键回滚到上一版，不能只记录一句\"上线成功\"就完事。部署用的 systemd 托管服务，Nginx 反代 + HTTPS，再配合定时备份，这套东西虽然不花哨，但很稳。\n\n回头总结，个人项目的核心竞争力从来不是界面多好看，而是它能不能一直演进下去。架构规范、分层约束、代码范式统一，这些前期花的时间，会在后面每一次迭代里加倍还给你。对一个要长期维护的项目来说，架构的\"可演进能力\"，比一时的功能多少重要得多。\n","2026-07-20T02:00:00Z",6,true,{"prev":18,"next":28},{"id":19,"slug":20,"title":21,"excerpt":22,"category":8,"tags":23,"publishedAt":27,"readingMinutes":15},32,"personal-site-feature-bloat","个人项目如何规避「功能废墟」工程问题","功能越加越多，项目却越来越难改，最后重构比重写还贵——这是我踩过的\"功能废墟\"坑。",[24,25,26],"个人网站","工程管理","维护","2026-05-15T02:00:00Z",null,[30,32,43],{"id":19,"slug":20,"title":21,"excerpt":22,"category":8,"tags":31,"publishedAt":27,"readingMinutes":15},[24,25,26],{"id":33,"slug":34,"title":35,"excerpt":36,"category":8,"tags":37,"publishedAt":42,"readingMinutes":15},24,"vue-request-cache-implementation","Vue 全局请求缓存工程级实现、过期策略与脏数据规避","给 Vue 项目加全局缓存时踩过的坑：过期策略、脏数据、并发去重，一篇讲清楚。",[38,39,40,41],"Vue","TypeScript","缓存","性能","2026-01-25T02:00:00Z",{"id":44,"slug":45,"title":46,"excerpt":47,"category":8,"tags":48,"publishedAt":50,"readingMinutes":15},31,"virtual-list-is-not-enough","前端性能优化：虚拟列表真实适用场景与完整优化链路","列表卡顿别急着上虚拟列表，先排查网络、数据、渲染整条链路，最后才是它。",[38,49,41],"虚拟列表","2025-12-10T02:00:00Z"]