记录一次博客新闻页与索引页的连续抢修(2026-10-14)
记录一次博客新闻页与索引页的连续抢修(2026-10-14)
今天最重要的判断:隧道这层既然没人守得住,就不该再让页面依赖它;索引页的空白不是布局问题,是模板只有一行函数。
10-13 那场 Cloudflare 隧道事故把 news 页打回原形,今天除了把新闻从“壳+API”改成静态快照,还把 archives、categories、tags 三个索引页从“没人看”改成“能导航”。整个过程里最值钱的不是改完,而是两个坑:curl 200 不代表图没挂,CSS Grid 的 1fr 会偷偷把移动端撑爆。
一、起点:隧道没救,新闻页得换条命
先把昨天的结论接上:cloudflared 边缘端口 7844 被本机出口 UDP+TCP 双封,12 个节点实测全部 TLS_FAIL,代理和 TUN 这条线整体废掉。用户今天报 demoblog.4everland.website/news/ 显示“新闻服务离线(新/旧域名均不可达)”,我第一反应是看 watchdog——结果它真没偷懒,只是三个盲区谁都没管到隧道这一层。blog 守卫按进程名判活,故障时 cloudflared 明明活着,只是在无限重试 dial 7844,却被判健康;yunpan 守卫计数不落盘,每分钟拉起的短命进程每轮重置,永远达不到重启阈值;news 自己的守卫只管 8800/8802/8803 和 fetcher,职责边界里没有隧道。看一眼 curl 127.0.0.1:8803 还是 200,6454 条数据完好,问题就只在隧道这层。既然隧道无人能救,干脆把 news 页改成静态快照 reader,不再依赖任何隧道 API。
二、把新闻页改成静态快照并接进每日刷新
改法分三块。数据层:tools/news/build_news.py 新增 --prev,让阈值以线上目录为准,DRY 跑时不落盘;调度层:.workbuddy/scripts/daily_refresh.sh 的 ONLY 默认值加 news,插入「3.5) 资讯快照」步骤——写 $TMP/news_out 中转,校验后 cp 到 source/news/,archive 先清空再落盘,报告里加 - 资讯:$R_NEWS;页面层:source/news/index.html 修两个真实缺陷。一个是 chip 计数与快照脱节:服务端 /api/channels 的 count 是全量统计(热点 447 / 财经 462),快照只留 300 条,导致「房产 7」点进去 0 条。改成 renderChannels() 按 DATA.feed 实际重算,chip 数字与点开后条数始终一致,现在就是全部 300 / 热点 12 / 财经 42 / 国际 7 / 房产 0。另一个是标题不反映分类,feedTitle 原本只反映搜索/日期,点分类后仍显示“最新资讯”;改成 搜索 > (分类 + 归档) > 最新 的优先序,示例「热点 · 2026-10-13 归档 · 1 条」。
三、上线验证:skip_render 不是坑,是两个可用性缺陷
部署时有个关键验证:skip_render: "news/**" 确实只跳渲染、仍复制文件,public/news/news.json 和 4 片归档都进了产物。之前 public/news/ 只有 index.html,是因为那时 json 还没生成,不是 _config.yml 的问题,所以不用改配置。git 提交 bf1a746(首次含 json)→ 6fb972d(页面修复)。线上三个域名都 200,Pages 生效约 50 秒,4everland 同步约 5 分钟。playwright 渲染零 JS 错误,热榜 10 / 卡片 30 / chips 15 / days 5,点卡片跳原文正常,搜索「日本」10 条,15 个频道逐个点筛通过,切日期按需加载归档分片也通过。末尾那条 spawn firebase ENOENT 还是已知长期失败,与内容无关。验证脚本留在 .workbuddy/tmp/ 里:news_live.js、news_chips.js、news_live_final.png。
四、索引页改造:about 挂图与三个页面的单调
同一天还处理了用户反馈的 /archives/、/categories/、/tags/ 内容单调和 /about/ 挂图。挂图那张我一开始用 curl 打,返回 200,但浏览器实际 REQFAIL——所以判断图片是否真挂,必须用 playwright 查 naturalWidth === 0,不能看状态码。已把 source/about/index.md 里那行带 <img> 的空 H1 整行删除。
三个索引页的情况比想象中惨:/categories/ 模板全文只有一行 list_categories(),渲染出来就是「Docs文檔 (54) / 分享 (3)」两行字;/tags/ 的 cloudTags() 随机排序、不显示篇数;/archives/ 是一条时间轴,同年不分月,per_page: 10 让 57 篇分 6 页。改造思路是不合并成一个页面,而是把三页做成互相可达的“站点索引”:新增 includes/mixins/index-nav.pug 提供 +indexStats() 和 +indexNav(),新增 source/css/index-pages.css 经 inject.head 注入。/archives/ 加年份直达胶囊(2023·8 / 2024·2 / 2026·47);/categories/ 改成卡片网格,每卡有名称、篇数、占比条、时间跨度、最近 6 篇标题;/tags/ 改成按篇数降序 + 右侧热门标签 Top 12;_config.yml 里把 archive_generator.per_page 从 10 改到 30,57 篇两页看完。
五、两个坑:年份锚点踩空,Grid 把移动端撑爆
第一个坑是年份 chips 用页内锚点。归档页会分页,锚点只落在当前页,第一版只在第一页条件下渲染,而第一页只有 2026,条件不成立,chips 根本不显示。改成链接 /archives/YYYY/,直接走 Hexo 自动生成的年份归档页,统计用全量 site.posts。第二个坑更隐蔽:CSS Grid 的 1fr 搭配默认 min-width: auto,分类卡片里 white-space: nowrap 的长标题把列宽从 337px 撑到 447px,移动端整页横向溢出,实测 bodyScrollW 466 > winW 375。修复就一行思路:.cat-card { min-width: 0 },移动端 grid-template-columns: minmax(0, 1fr)。线上提交 aa42dc3,163 files 变更,/archives/2023|2024|2026/ 全部 200,旧分页 /archives/page/3..6/ 已经 404——这是 hexo clean 后全量重建才清掉的,增量构建会残留旧文件。
六、顺手拆掉硬编码分类,填掉主列空白
索引页改完,用户又指出 categories 里“docs 文章为啥这么多”。根因是 tools/articles/build_articles.py 第 262 行硬编码 categories: Docs文檔,47 篇自动文章全挤进一个分类。新增 tools/articles/taxonomy.py 作为规则单一真源,tools/articles/reclassify.py 做存量回填,只改 categories: 一行、幂等可逆。改完量化策略 14 篇,其他分类也按内容分开了。期间还量化过空白:categories 主列底 1146 vs 侧栏底 3004,空白 1858px;tags 空白 1811px。补了标签速览、分类速览、月度发布分布等区块,后来因为双列网格右半幅空得难看,改成单列全宽表格式索引。用户最后反馈“和最上面的栏位重复了,去掉”,那只是他们没刷新,去掉重复栏位后收尾。
七、遗留与下一步
books 页还是老样子:source/books/index.html 仍依赖同一隧道,READER_PRIMARY=books.demohappy.top、READER_FALLBACK=books.demo.fan,列表读本地 books.json(932 本)能渲染,但“在线阅读/下载”按钮点进去是 CF 530,而且探活是静默失败,全挂时只回退 FALLBACK,不给用户任何提示。待定:加离线态提示或直接静态化。另外 firebase ENOENT 那个失败继续留着不折腾;check_ind 那批验证脚本还在 .workbuddy/tmp/,下次改动前可以复用。今天最大的收获是:能用静态快照绕开的,就别再去赌隧道那条死路。