一直觉得这个博客有点花里胡哨。用的是别人的 Aurora 主题,SPA 架构——首页 HTML 只有 1,539 字节,一个空壳,真正的页面靠 453,646 字节的 JS 在浏览器里拼出来。

所以这次的想法很简单:自己写个主题,干干净净的。

主题两天就写完了。真正的活是在后面。

一、删掉的东西还在,因为服务器在兜底

新主题上线后首页 9.9KB,正文直接躺在 HTML 里,全站 0 个 JS。按理说这就清爽了。

然后我随手请求了一下 /lodash.min.js——这是 Aurora 时代引用的文件,主题删了,文件也不在服务器上。结果:

1
200 OK

/js/md5.min.js、/dist/gitalk.min.js 也一样,全是 200。

那一刻我意识到:旧站那些”加载不出来但不报错”的怪现象,原因不在主题,在服务器。 页面上引用的三个脚本早就 404 了,是 nginx 把所有找不到的路径统一甩回 index.html,返回 200,浏览器拿到一坨空壳当脚本执行,什么也没发生,静悄悄地失败。

这就是 SPA 时代的标配配置。前端接管路由之后,服务器不知道哪些路径是真页面、哪些不存在,只能全都交出去——反正前端会渲染。可一旦换回服务端生成的静态站,这个兜底就从”必要”变成了纯粹的负担。

二、所有错误地址都返回 200

顺着上一条查下去,就是软 404(soft 404)。

打开 /etc/nginx/sites-available/ckun.xyz,第 17 行:

1
2
location / {
try_files $uri $uri/ /index.html;

再往下翻,第 38 行:

1
error_page 404 /404.html;

这行一直在,只是从来没生效过。 因为 try_files 的第三个参数兜住了所有失败情况,nginx 根本走不到 404 分支——它认为自己成功返回了首页。

改动就一个字符级别的事:

1
2
- try_files $uri $uri/ /index.html;
+ try_files $uri $uri/ =404;

效果:

请求 改前 改后
/ 200 200
/tiantian/ 200 200
/definitely-not-exist-xyz/ 200 + 首页 404 + 404 页
/post/不存在.html 200 + 首页 404 + 404 页

为什么值得修:搜索引擎遇到这种配置,会把无数垃圾 URL 当成有效页面收录进去,稀释站点权重。用户输错地址看不到提示,只看到首页,还会以为是自己点错了。

改之前 cp -a 备份、diff -q 确认备份一致,改完 nginx -t 过了再 systemctl reload(不是 restart)。这些动作一个都不能省——这是生产机上唯一对外的那台。

三、证书从来不会自动续期

修完 404,顺手检查了 www 子域,发现 https://www.ckun.xyz 直接报证书不匹配:

1
2
subject: CN=ckun.xyz
subjectAltName does not match hostname www.ckun.xyz

证书是单域名的。nginx 里 server_name ckun.xyz www.ckun.xyz; 两个都收了,DNS 也指向同一台机器,但证书只签了主域。一条命令扩进去就行:

1
certbot certonly --nginx -d ckun.xyz -d www.ckun.xyz --expand

然后是这次最值得说的一条:这台机器上所有证书都不会自动续期。

我当时想确认续期有没有配好,结果三处全空:

检查项 结果
systemctl list-timers | grep certbot 空
/etc/cron.d/ 里 certbot 无
snap list | grep certbot 无
root 的 crontab 无

查根因:

1
2
$ dpkg -S /usr/bin/certbot
dpkg-query: no path found matching pattern /usr/bin/certbot

certbot 不是 apt 装的,所以包里自带的 systemd timer 根本没跟着装上。机器上 12 张证书——主域加上 aria2、ariarpc 这些子域——全靠人手续。哪天忘了,某天早上所有 HTTPS 会同时失效,而且是在一个你完全没预料到的时刻。

补了个 cron 兜底:

1
0 3,15 * * * root certbot renew --quiet --deploy-hook 'systemctl reload nginx'

一天两次检查(certbot 只在到期前 30 天才真去申请),续完自动 reload 让 nginx 加载新证书。

这类问题有个共同点:它不在你正在做的事的路径上。 你不查 www 就发现不了单域名证书,不修证书就不会去看续期配置。每一次都是”顺手多看一眼”才挖出来的。

四、”图太大”的根因是截图里的空白

写小程序介绍页时,用户反馈”图太大了,一排多放几个”。

第一反应是调 CSS,把显示宽度从 290px 降到 200px,组内从 250px 降到 165px。改完还是觉得占地方。

真正的原因在图片本身:手机截图是 819×1920 的整屏,但表单页内容到”保存”按钮就结束了——下面七八百像素全是空背景。显示宽度怎么调,这块空白都按比例跟着。

处理办法是先裁再缩:

  1. 缩到 140px 宽,从底部往上逐行扫,找第一行满足 max(行) - min(行) > 18 的位置,那就是内容底
  2. 裁掉下方多余部分,只留 34px 余白
  3. 转 WebP(620px 宽、q80)

结果:

图 裁前高 裁后高 减少
记录血压 1920 1368 29%
记录血糖 1920 1222 36%
记录尿酸 1920 1198 38%

9 张图从 887KB 降到 317KB。二维码例外——保持原尺寸不压缩,码糊了就扫不出来。

顺带一个细节:裁完三张图高度不一致,display: flex 的默认 align-items 会让它们错位,得显式设 flex-start 顶对齐。

五、几个零碎的坑

Hexo 会把 scripts/ 下所有文件当成插件脚本加载。 我往 scripts/ 放了个 validate_posts.py,结果每次构建都报:

1
2
ERROR Script load failed: scripts/validate_posts.py
SyntaxError: Invalid or unexpected token

它拿 JS 的解析器去读 Python 文件了。挪到 tools/ 就安静了。这个错误不阻断构建,所以我用了好几天都没发现——只是被 | tail -2 挡住了。

CSS 变量写错不会报错,只会静默失效。 我写了 color: var(--text),而主题里的变量叫 --fg。没有警告,没有回退色,就是这一条样式悄悄不生效。后来是通读变量表才发现的。

DNS 被 fake-ip 劫持时,直连域名会连到保留网段。 想 SSH 上服务器,先 getent hosts ckun.xyz 得到 198.18.0.138——这是 198.18.0.0/15,专门做 benchmark 测试的保留段,代理的 fake-ip 占用了它。SSH 连域名必然失败,得用 DoH 查真实 IP:

1
curl -s 'https://dns.alidns.com/resolve?name=ckun.xyz&type=A'

收尾

这次一共改了:主题重写、布局从 720 加宽到 1100、标签配色、分页 bug、一个介绍页配 11 张图、nginx 的软 404、证书扩容、续期 cron。

可如果只看”想简单点”这个出发点,主题本身大概占三成工作量。剩下七成是主题之外那些一直存在、但没人碰过的东西——SPA 时代的兜底规则、装歪了的 certbot、塞在配置 JSON 里的 secret、以及一张张没人裁剪的截图。

它们不是新问题,只是没人在你修别的东西时顺手看它们一眼。