工程实践 · 2025年1月19日 · 3 分钟

Bing'Works 全栈架构设计与演进

一个零依赖个人站点的架构回顾:为什么不用框架、数据层设计、搜索与主题切换的权衡。

Bing'Works 这个个人站点断断续续维护了几年。在经历过两次重构之后,我想写一篇文章回顾一下架构上的决策——哪些做对了,哪些是过度设计,以及背后为什么这么权衡。对于一个"小而美"的个人项目,很多决策和公司项目是相反的,这个对比本身就挺有意思。

技术栈全景

┌────────────────────────────────────────────────┐
│                  Nginx (静态服务)                │
│              /         │          \             │
│         index.html    css/        js/           │
│              │                                  │
│      ┌───────┴────────┐                         │
│      │   Hash Router   │                        │
│      │  (纯前端路由)    │                        │
│      └───────┬────────┘                         │
│              │                                  │
│    ┌─────────┼─────────┐                        │
│    │         │         │                        │
│    ▼         ▼         ▼                        │
│  Works     Blog      About                      │
│  (静态)   (数据驱动)   (静态)                    │
│              │                                  │
│    ┌─────────┼─────────┐                        │
│    ▼         ▼         ▼                        │
│ Markdown   语法高亮   评论系统                    │
│  解析器     渲染器    (GitHub Issues)            │
└────────────────────────────────────────────────┘

核心理念:零构建工具、零运行时依赖、零后端服务。 一个 HTML 文件 + CSS + ES Modules,部署就是 scp 到服务器上 Nginx 的静态目录。

为什么不用框架

这个决策每次重构都会被重新审视,但每次都得出同样的结论。

维度 React/Vue/Next.js 零依赖方案
构建产物 50KB-200KB+ JS ~15KB JS(含所有模块)
冷启动 水合、渲染 零延迟,HTML 直出
部署 构建流程、CDN rsync 一行命令
维护成本 依赖升级、安全补丁 依赖是浏览器标准

对于内容型站点,热更新的价值远不如公司级 SaaS 产品那么大。内容不常变,访问量和交互密度也不高,框架带来的复杂性远超其收益。

但这个决策有一个隐藏成本——生态缺失。Markdown 解析、代码高亮、路由,全得自己写。好在这些模块的单体规模都很小(每个 100-200 行),自己写的代码出了问题能立刻定位,不用翻 GitHub Issues 猜依赖包的 bug。

数据层:无后端不等于无数据

博客文章的数据流设计:

posts/*.md ──→ 用户按需 fetch ──→ 自定义 Markdown 解析器
      │                                    │
      ▼                                    ▼
data/posts.json ──→ 页面加载时 fetch ──→ 列表渲染 + 搜索索引

posts.json 是所有文章的元数据(标题、日期、标签、摘要),大约 8KB,页面首次加载就全部拿到,列表渲染和搜索在客户端毫秒级完成——没有网络往返。

文章正文按需加载——用户点击具体文章时才 fetch 对应的 .md 文件,在浏览器端解析渲染。这个策略叫 static + on-demand:列表数据预热,正文懒加载。

几个有意思的细节

主题切换:用 data-theme 属性 + CSS 变量实现。不用 JS 注入样式,而是切换 HTML 属性让所有变量一次性生效。用户的偏好存 localStorage,加载时在 <head> 里同步读避免闪烁。

视图切换:列表视图和卡片视图之间的切换,本质是切换 CSS Grid 的列数和卡片样式。数据层完全不变,只是展示层的 class 变动。这种"纯 UI 状态"不经过路由、不触发数据重新加载。

搜索:全客户端搜索。posts.json 里的摘要已经够用,搜索时对标题和摘要做简单的字符串匹配(考虑中文分词的话会复杂一些,但目前文章量级不需要)。没有后端搜索服务的延迟,打字即时出结果。

踩过的坑

第一次重构时做过的最蠢的事是引入了一个构建工具链——Webpack + Babel + PostCSS。构建时间 15 秒,热更新偶尔失效,配置文件越写越长。一个内容站搞成这样,纯属过度设计。

第二次重构时学乖了,回归"浏览器原生支持什么就用什么"。ES Modules 所有主流浏览器都支持了,CSS 变量和 Grid 在 2020 年就稳定了,Hash Router 不需要 History API 的服务器配置支持。技术选型的金线:先问自己"浏览器能不能直接干"。

最后

这个站点的架构哲学:每一个引入的复杂性,都必须解决一个真实存在且不可回避的问题。 大多数个人站点的问题不是"技术不够先进",而是"技术太厚重,反而拖累了内容本身"。

轻量不等于简陋——它是经过权衡后的精确选择。

继续阅读

评论