跳到正文

Colophon

本站技术说明

既然整个站都在讲「为什么这么设计、代价是什么」,那它自己也该经得起同样的追问。下面的数字全部是构建产物的实测值,可以用浏览器开发者工具当场核对。

4.4 KB
首页 HTML

brotli 后

19.8 KB
最重页面的 JS 总量

含交互运行时

8.6 KB
交互运行时

Preact,替代 49.6 KB 的 React

0
第三方脚本

无统计、无字体 CDN、无外链

技术栈

框架
Astro · Preact islands · Tailwind · TypeScript
图表
手写 SVG,无图表库
字体
Source Serif 4 · Inter · 思源宋体子集 · JetBrains Mono,全部自托管
服务端
Caddy · Docker Compose · Debian
部署
GitHub Actions → 构建 → 脱敏门禁 → rsync 原子切换 → 健康检查 → 失败回滚
托管
阿里云轻量 2 核 / 893 MiB / 东京,域名在 Cloudflare(DNS-only)

决策与代价

每条都写了放弃的方案和付出的代价 —— 只写选了什么,说明不了判断力。

静态优先:内容是死的,交互才需要 JS

问题

这个站 95% 是文字与图表,只有链路图、时序回放、解码器三处需要交互。用一套 SPA 框架渲染全部内容,等于让读者为三个组件下载整个运行时。

备选方案

  • 传统 SPA:一次性下发整个应用,首屏要等 JS 执行完
  • 全静态 HTML:最快,但三个交互组件没法做
  • Astro islands:整页静态 HTML,只有标注为交互的组件下发并各自独立激活

选择

Astro islands。页面以静态 HTML 交付,三个交互组件用 client:visible 声明,滚动到才加载。没滚到解码器那一节,它的代码就不会被下载。

代价

交互组件之间不能直接共享内存状态,跨组件通信要走事件或 URL。本站没有这类需求,代价为零。

把 React 换成 Preact,省下 41 KB

问题

三个交互组件加起来只有 6.5 KB,React 运行时却要 49.6 KB —— 运行时是自身代码的近八倍,比例不合理。

备选方案

  • 保留 React:生态最大,但这个站一个生态特性都没用到
  • 改用原生 Web Components:运行时为零,但要自己实现状态与差量更新
  • Preact:API 兼容,三个组件只用了 useState / useEffect / useRef / useMemo

选择

Preact(compat 模式)。组件代码一行未改,运行时从 49.6 KB 降到 8.6 KB。

代价

少数依赖 React 内部实现的三方库不兼容。本站没有这类依赖;若将来需要,可以只把那一个组件切回 React。

图表全部手写 SVG,不引入图表库

问题

需要三种图:数据链路图、时序回放、状态机。现成方案里 React Flow 约 120 KB,Mermaid 约 500 KB。

备选方案

  • React Flow:拖拽、缩放、自动布局都有 —— 但这三张图都是固定布局,一个都用不上
  • Mermaid:写法简单,但输出静态图,无法逐步播放,也点不开单条消息看报文
  • 手写 SVG:完全可控,但要自己算路径与布局

选择

手写 SVG。三个组件合计 6.5 KB,比单个图表库小一到两个数量级,且三张图共用同一套视觉语言。时序回放的逐步播放、点消息展开报文,用现成库反而做不到。

代价

节点坐标是手工排的,图变复杂时维护成本会上升。20 个节点以内手排比自动布局更好看,超过就该换方案了。

中文标题字体:按实际用字在构建期切子集

问题

思源宋体完整字库 25 MB。中文站想用衬线标题,这是首屏性能的头号杀手 —— 而正文若也用网络字体,代价还要再翻几倍。

备选方案

  • 全用系统字体:零成本,但 Windows 上的中文衬线体观感很差
  • 按 unicode-range 分片:浏览器按需取片,但一页仍可能触发十几个请求
  • 构建期扫描产物,只把标题里实际出现的汉字打进子集

选择

正文走系统字体栈,零下载;标题在 astro build 之后扫描产物中 h1–h3 与显示级元素的实际用字,用 harfbuzz 生成子集,字重钉死输出静态实例。当前 609 字、104 KB,是全站唯一预加载的字体。

代价

新增页面若引入大量新字,子集会变大。构建脚本内置 160 KB 硬门禁,一旦扫描范围跑偏(比如误抓正文)直接构建失败。

压缩放在构建期,不放在请求时

问题

Caddy 官方镜像不含 brotli 编码器;即使自己编译进去,运行时压缩为了控制延迟也只能用中等级别。

备选方案

  • 自编译 Caddy 加 brotli 插件:每次请求现压,CPU 换带宽
  • 只用 gzip:镜像自带,但压缩率明显差一截
  • 构建期用最高级别压好,服务端直接发压缩文件

选择

构建期生成 .br / .gz / .zst 三份,Caddy 按 Accept-Encoding 直接发对应文件。静态站的内容在构建时就定了,没有任何理由每次请求重压一遍。整体省 74%。

代价

构建时间增加几秒,产物文件数变成四倍。都不重要。

数据库选 SQLite,不选 Postgres

问题

站点要存的只有文章与访问统计,几万行量级、单写入者。服务器物理内存 893 MiB,已用约 466 MiB。

备选方案

  • PostgreSQL:功能完整,但默认配置常驻 150–200 MiB,且要多维护一个容器与一套备份恢复流程
  • SQLite:进程内库,常驻内存接近零,备份就是复制一个文件

选择

SQLite(WAL 模式)。这个负载下它的写入吞吐远超需要,省下的内存留给构建与突发流量。

代价

单机绑定,没法水平扩展,也不支持多进程并发写。这个站不会有第二台机器,所以代价不成立 —— 真需要时再迁移,schema 是通用 SQL。

静态资源不经过后端

问题

后端一旦崩溃或重启,整站是否跟着不可访问?对简历站来说,可用性优先于一切动态功能。

备选方案

  • 全部请求走后端:路由统一,但后端挂了就白屏
  • 反向代理只把 API 路径交给后端,静态文件由代理直接读磁盘返回

选择

Caddy 直接读文件返回静态内容,只有 /api 与后台路径转发给后端。后端挂掉时,页面照常打开,只是统计不再记录。

代价

后端无法对静态页面做鉴权或改写。本站不需要。

服务器:标称 1 GB,实际只有 701 MiB

问题

开机后 free 显示物理内存只有 701 MiB,比标称少了近三分之一。第一反应是被少配了。

备选方案

  • 找服务商申诉:先确认是不是真的少给
  • 自己查内存去了哪

选择

查出来是内核启动参数里的 crashkernel 预留:镜像的规则让 1 GB 机器落进「1–4 GB」档,为崩溃转储预留了 192 MiB。硬件层面 1 GB 一点没少。个人站点永远不会去分析崩溃转储,改成不预留后重启,可用内存回到 893 MiB。

代价

失去内核崩溃时自动保存转储的能力。对一台只跑静态站的机器,这个能力换 192 MiB 内存不划算。

源码

仓库暂为私有:其中包含简历原始数据。若你想看某一部分的实现, 发邮件告诉我是哪一块,我可以单独整理出来。