前言
ByteFisher 博客从最初的 Hexo 基础静态站一路迭代,到现在已经覆盖了技术教程、钓鱼日记、钓鱼相册、鱼获 Dashboard、钓点地图、抖音视频、AI 小游戏、AI 问答助手、热门文章、PWA 等多个功能模块。文章总量突破 150+,线下工具脚本接近 20 个,第三方服务依赖 6 个以上。
功能越来越多,但问题也随之暴露:
- 首页还是时间流——150 篇文章按时间排列,新访客不知道从哪开始看。
- 文章摘要参差不齐——有的空着,有的截断,有的塞了正文片段。
- 旧图床链接零星残留——以前用 iili.io,切到 img.bytefisher.top 后还有漏网之鱼。
- tools 目录积灰——脚本越来越多,三分之一已经不再使用。
- 基础设施欠账——PJAX cacheBust 没开、PWA 缓存策略过时、CI/CD timezone 没设。
本轮优化的核心理念是”收口”:不追加新功能,对已上线的内容、配置、代码做系统性清理。本文记录这次优化的完整过程。
一、信息架构重组:从时间流到入口矩阵
1.1 背景:一个首页只有时间线的博客
优化前的 index.swig 结构很简单:
1 | {% block content %} |
Hero 区域放了几个跳转按钮和技术统计,下面就是按日期排列的文章列表。对于知道自己在找什么的老读者没问题,但新访客面对 150 篇文章的时间流,往往不知所措。
1.2 设计思路:按入口引导 + 专题推荐
参考一些内容型网站的布局方式,把首页从上到下拆成四个区域:
- Hero 品牌区——保留头像、打字动画和全站数据统计
- 内容入口 Grid——教程、钓鱼、游戏、视频四张卡片,按主题分流
- 精选专题——游戏开发、C# 基础、Hexo 实战、AI 编程四个系列推荐
- 最新文章 + Featured 推荐
关键是第二和第三区——它们让新访客在 1 秒内理解”这个博客有什么”,并且两次点击就能进入任意核心系列。
1.3 实现:home-sections.swig
新增 themes/next/layout/_partials/home-sections.swig,内容区按模块切分:
1 | {# 内容入口 — 四张卡片按主题分流 #} |
对应的 CSS 全部写在 source/_data/styles.styl 中,采用 Grid 布局实现响应式卡片:
1 | .home-entry-grid { |
1.4 Featured Posts:让精选文章浮出水面
新增 featured-posts.swig,逻辑分两层:
1 | {% for post in site.posts.sort('date', -1).toArray() %} |
也就是说,只要在文章的 front-matter 中设置 featured: true,它就会自动出现在首页的”推荐阅读”区域。没有手动设置的文章,则自动拿最新 4 篇填充——完全不增加维护成本。
1.5 全站数据联动:教程计数打通 Hero、Sidebar、菜单
为了让 Hero 区域的统计数据真实反映博客内容,需要把教程系列数量透传到前端。原来这些数字是手写在配置里的。
在 themes/next/scripts/ 中新增教程计数逻辑,通过 Hexo 的 filter 在生成阶段自动计算:
1 | // themes/next/scripts/filters/tutorials-data.js |
这样 Hero stat、侧边栏 site-overview、菜单 badge 都能用 {{ theme.tutorialCount or 0 }} 获取真实数据,不再需要手动维护。
效果:首页从纯时间流变成有层次的信息入口,新访客可以在两次点击内进入任意核心系列。后续统计显示,教程页的访问占比提升了约 30%。
二、SEO 与内容治理:14+ 篇文章摘要重写
2.1 问题:description 质量参差不齐
经过前几轮优化,文章 description 的覆盖率已经很高(95% 以上),但质量不行。
用 rg -n "^description:" source/_posts -g "*.md" 逐一检查,发现几类典型问题:
| 问题类型 | 示例 | 影响 |
|---|---|---|
| 空描述 | description:(仅字段名,无内容) |
搜索引擎用正文前 160 字,可能截断不完整 |
| 截断残片 | 以半句话结尾,有明显断句痕迹 | 搜索结果摘要读不通 |
| 正文污染 | 混入图片描述、代码片段、链接 URL | 降低搜索引擎对页面主题的判断 |
| 过长 | 超过 160 字,搜索引擎也会截断 | 关键信息可能被截掉 |
2.2 工具:frontmatter-audit.py
写了一个审计脚本 tools/seo/frontmatter-audit.py,自动扫描所有文章的 front-matter:
1 | POSTS_DIR = Path(__file__).resolve().parents[2] / "source" / "_posts" |
运行一次就会列出所有有问题的文章,逐一修复。这个脚本只读不改,可以放心集成到发布检查流程中。
2.3 重写策略:按文章类型定模板
针对不同类型的文章,制定了对应摘要模板:
- 技术教程:
[系列名] + [这篇文章教什么] + [读者收益] - 钓鱼日记:
[时间] + [地点] + [鱼获亮点] + [经验总结] - Hexo 实战:
[解决的问题] + [方案简介] + [涉及技术/工具] - 游戏开发:
[游戏类型] + [开发技术] + [核心功能点]
示例——之前 C#学习笔记【结构体Struct】 的描述是空字段,重写后:
1 | description: 'C# 学习笔记系列第 5 篇。讲解结构体(struct)的值类型特性、与类的区别、适用场景和最佳实践,包含栈上分配和装箱拆箱的性能要点。' |
14+ 篇文章的描述逐一按这个标准重写,同时文中关键词也同步做了归并统一。
三、废弃资产清理
3.1 Program 页面:停用废弃布局
source/program/index.md 使用了 layout: "program",这是一个历史遗留的自定义布局,已经被新搭建的 /tutorials/ 教程总入口页面取代了。但旧 URL /program/ 可能还在搜索引擎索引中,访问后返回一个不再维护的页面。
处理方式不是直接删除,而是改为静态跳转页:
1 |
|
加上 canonical 和 noindex,搜索引擎知道该页面已不再独立索引。
3.2 旧图床链接清零
两个月前把钓鱼相册和文章配图从 iili.io 迁移到了自建的 img.bytefisher.top CDN,但 source/douyin/videos.json 中还残留着 22 条旧链接。
用 rg "iili\.io" source 快速定位所有残留,逐条迁移:
1 | # 检查旧图床残留 |
将 22 条封面链接全部替换为 https://img.bytefisher.top/douyin/xxx.webp,再次运行 rg 零结果。
3.3 Tools 目录瘦身:一次性删除 16 个历史文件
这是最”爽”的一步。tools/ 目录经过多轮迭代,积累了大量不再使用的脚本:
archive/js/下的download_images.js、download_compress_images.js、download_missing.js、fix_webp_urls.js、replace_image_urls.js——这些是图床迁移时的临时工具,迁移完成后再无用途archive/python/下的fix_series_brackets.py、fix_series_format.py、fix_series_indent.py——历史格式修复脚本,修复过一次后不再需要- 整个
wechat-path/目录——公众号图片路径处理工具,对应的逻辑已经在主同步工具wechat-sync/index.js内实现了
清理了 16 个文件、约 800 行代码,tools/ 目录从杂乱状态变得清爽:
1 | tools/ |
四、基础设施加固
4.1 PJAX CacheBust:修复文章消失 Bug
线上发现一个诡异问题:新发布的文章”ByteFisher AI 编程实战(二)”首次加载正常显示,切换到其他页面再回主页就消失了,刷新后又出现。
追到 themes/next/layout/_scripts/pjax.swig 发现 cacheBust: false。PJAX 的 XHR 请求不会加时间戳参数,浏览器直接返回了 HTTP 缓存中的旧首页 HTML——那里面没有新文章。
1 | // 修改前 |
同时加了一道 opacity 安全兜底,防止 motion 动画未完成时 post-block 卡在不可见状态:
1 | // 在 pjax:success 处理器末尾 |
4.2 PWA Service Worker 缓存策略升级
Service Worker 之前用的 CACHE_NAME = 'bytefisher-v4' 硬编码版本号,每次更新都需要手动改。改为自动生成:
1 | // source/sw.js |
缓存策略也从单一的 cache-first 改为分级策略:
1 | self.addEventListener('fetch', function(event) { |
HTML 和 CSS 采用网络优先策略保证内容时效性;JS、图片、字体采用 stale-while-revalidate(同时用缓存响应和发起网络更新),兼顾速度与更新。
4.3 CI/CD 定时构建与时区
GitHub Actions 的定时任务之前没有设置时区,凌晨触发的时间点不符合预期。在 workflow 文件中加入环境变量:
1 | env: |
第一个 cron 在午夜过 5 分钟触发,目的是让未来日期文章(比如当天 0 点定时发布的文章)被自动构建上线。第二个作为白天的容错构建。
4.4 明确未来文章发布策略
博客有一个系列 ByteFisher AI 编程实战教程,从第 1 篇到第 21 篇预写了全部文章,日期跨度从 2026-06-08 到 2026-08-17。主配置 _config.yml 保持 future: false,所以普通 npm run build 不会生成这些未来文章。
但项目中又存在 build:all 和 server:all 脚本用于开发预览。如果不明确说明,容易导致两个问题:
- 开发时看不到未来文章,以为构建出错
- 生产构建意外包含了不应该发布的内容
在 AGENTS.md 中补充说明:
1 | ### 未来文章发布策略 |
这样每个环节都有明确的行为预期。
五、工具链优化
5.1 WeChat Sync:40007 错误与章节截断
公众号同步工具 tools/wechat-sync/index.js 在同步长文时遇到了两个问题:
问题一:40007 错误
调用微信 API updateDraft 时传参格式错误:
1 | // 错误写法——返回 40007 invalid args |
一行括号的差别,排查了半天。
问题二:长文章截断后标签不闭合
微信图文消息有正文长度限制,超长文章需要截断。但原来的 truncateHtml() 函数只按字符数切,切完后的 HTML 片段可能不闭合标签,导致微信渲染出错。
改为语义截断——按 <h2> 章节边界截断:
1 | function truncateAtSection(html, maxLen) { |
这样即使截断,每段导出的内容也是语法完整的 HTML。
六、总结
这次收口型优化前后涉及了 30+ 个文件的修改,横跨信息架构、SEO、废弃资产、基础设施、工具链五个维度。几个核心数据:
| 维度 | 内容 |
|---|---|
| 新增 | 首页内容入口 Grid + 精选专题 + Featured Posts |
| 重写 | 14+ 篇文章描述,按系列统一摘要模板 |
| 清理 | 16 个无用脚本(800+ 行代码)、22 条旧图床链接、1 个废弃页面 |
| 修复 | PJAX cacheBust、WeChat 40007、Service Worker 版本号 |
| 新增工具 | frontmatter-audit.py(SEO 审计,只读) |
| 文档 | 收口文档、未来文章发布策略、发布检查流程 |
下一轮(P3)重点将转向旧文翻新和 RSS/Sitemap 定期校验——但那是另一个故事了。
参考
- 收口型优化需求文档:
docs/博客收口型优化需求文档.md - 核心涉及提交:
f67d92e、eec7bc2、46aa669、72188d3、8e184fa - 前篇: Hexo 博客三项基础修复
- 前前篇: Hexo 博客体验升级