博客无效请求优化记录:从生成态审计到浏览器验证
前言
做博客时,“页面能正常打开”很容易让人产生一种错觉:既然正文、封面和动画都出来了,资源应该没有什么问题。
但浏览器并不这么想。它会照着 HTML、CSS、主题配置和脚本中的地址认真发请求;一个页面视觉上没有明显异常,不代表它没有在后台请求旧图片、错误 fallback,甚至一个拼接出了 undefined 版本号的脚本地址。
这次检查博客资源时,我遇到的就不是某一个孤立的 404,而是一组来自不同层次的小问题:主题功能开关、历史 WebP 转换遗留的扩展名、友链头像、Markdown 标记,以及文章资源目录中的媒体类型写错。它们单看都不复杂,混在一起以后却很难只靠肉眼排干净。
所以这次更新的目标不是“把所有外部链接都改掉”,也不是为了写一个漂亮的零错误数字,而是在不改变文章内容语义、不伪造缺失原图、不用无关图片顶替原资源的前提下,让所有能由仓库现有文件确认的本地资源引用回到正确状态。
本文的数据来自一次固定版本的本地构建结果、生成态资源审计和六个代表页的 Headless Chrome 验证。它能说明本次修复的 URL 在这些条件下不再触发本地资源错误;但不等同于所有生产网络、所有第三方 CDN、所有懒加载时机或真实设备上都永远不会失败。
先别急着改:这次到底要解决什么
无效请求并不全是同一种问题。比如一个站内图片路径写错,仓库里又刚好有同名的正确文件,这类问题可以安全修;而一张多年前引用的外部正文原图已经 404,仓库和 Git 历史里也没有原文件,就不能随便找一张“看起来差不多”的图片换上去。
在真正修改前,我把边界先定成了下面这样:
| 目标 | 本次的处理方式 |
|---|---|
| 站内本地资源 | 构建后审计,能够确认的缺失目标应归零 |
| 主题配置 | 只关闭当前没有实际内容使用、却会产生错误请求的功能 |
| 历史资源格式 | 仅在同名 WebP 或其他现存资源已确认时修正路径 |
| Markdown 内容 | 修正语法和媒体类型,不改变文章想表达的内容 |
| 外部原图 | 找不到原始文件时不伪造替代内容,单独记录遗留项 |
| 验证方式 | 不能只搜源码;需要检查生成 HTML/CSS 和浏览器真实请求 |
| 结论范围 | 区分“本地缺失已清零”和“外部资源仍可能受网络影响” |
这个边界看起来有点保守,但它能避免资源治理变成“为了消灭 404,悄悄把内容换成别的东西”。对于博客文章,图片本身也是内容的一部分;资源是否能加载和内容是否仍然真实,是两个都不能丢掉的条件。
为什么只搜源码还不够
最开始我也想过直接在仓库里搜索 .png、.jpg 和 .webp,把明显不对的路径改掉。但 Hexo 站点最终交给浏览器的是生成后的页面,而不是 Markdown 源文件本身。
一个真实请求可能来自很多地方:
- 文章 Markdown、内嵌 HTML 和文章资源目录;
- 友链数据文件中的头像地址;
- 主题配置中的默认页头图、错误兜底图;
- 主题模板根据开关动态插入的第三方脚本;
- HTML 的
src、href、poster、懒加载属性、srcset和内联style; - 生成 CSS 中的
url()。
也就是说,源码里没有一行“请求这个地址”的字面量,并不能证明浏览器最终不会去请求它。主题会把配置展开,插件会处理文章资源目录,Markdown 也可能因为一个全角符号而输出完全不同的 HTML。
这次新增的审计思路,就是先构建站点,再从生成目录中收集资源引用。它的核心不是“猜哪些图片可能坏了”,而是把最终页面里真正出现的地址分类:
1 | const HTML_ATTRIBUTE_PATTERN = |
随后,对站内相对地址解析到生成目录对应的文件;对直接外部媒体则单独做有限的可达性探测。这样至少可以把“浏览器可能请求什么”和“本地文件是否存在”放到同一张表里看。
一个没有图表的开关,为什么会影响全站
本次最集中的问题来自 Mermaid。
博客当前并没有使用 Mermaid 图表,但主题配置曾保留了 Mermaid 开关。主题 footer 会根据这个开关插入相关脚本;与此同时,版本字段没有配置,最终生成的地址里出现了一个没有版本号的 Mermaid 脚本请求。
这不是某篇文章的内容错误,而是一个配置分支在全站范围内被触发。审计时,178 个页面都带上了这条无效请求。
这里的解决方式不是手工删每个页面里的脚本,也不是在 footer 里硬编码一个版本号,而是回到功能本身:既然当前站内没有 Mermaid 图表,就关闭这个未使用的功能开关。
1 | mermaid: |
这个改动的重点在于:配置应当反映实际使用状态。 一个功能没有内容依赖时,保持它开启不叫“预留能力”,它更可能是在给每个页面增加不必要的加载路径和排查成本。
历史格式迁移,最容易留下“看不见的旧地址”
博客里的图片曾经历过 WebP 转换。转换本身没有问题,问题是有些配置和数据仍保留着旧的 .png、.jpg 或 .gif 地址。
其中比较典型的是主题里的默认页头图和图片加载失败时使用的 fallback。它们并不一定在每次正常访问中显眼:fallback 往往只有图片加载失败时才会真的走到;但只要它写的是不存在的旧地址,原本的一次图片失败就会再叠加一次失败请求。
本次对这类地址的处理原则很简单:不根据文件名猜,先确认同源目标真实存在。
1 | # 页面未单独配置头图时使用的默认图 |
同样的规则也用在友链头像上。对于已经存在同名 WebP 文件的头像,修正数据文件中的旧扩展名;对于一个外部头像,当前探测只得到连接失败,无法确认它是永久失效、临时网络波动还是对探测方式有限制,就不把它粗暴替换掉。
这比“所有外链统一换成站内默认头像”多一点麻烦,却能保留友链原本的身份信息,也不会把临时网络问题误判成内容错误。
有些请求问题,其实是 Markdown 写错了一个字符
资源路径正确,不代表最终会被当成图片或视频。
这次检查中有几处 Markdown 使用了全角感叹号:
1 |  |
它看起来和普通图片语法只差一点,但 Markdown 解析器认的是半角 !。全角写法不会得到预期的 <img>,而是留下普通文本和链接;这既让文章展示不符合预期,也让“资源有没有被页面实际加载”变得难以判断。
修正后是普通的 Markdown 图片语法:
1 |  |
另一个例子是文章资源目录里的媒体类型。某段内容引用了不存在的 .webp 文件,但同目录实际保存的是 .mp4 视频。这里不应该为了让路径“看起来像图片”去复制或伪造文件,而是让 HTML 表达它本来的媒体类型:
1 | <video controls preload="metadata"> |
这类修改没有改变文章内容,只是让源码、资源目录和最终 DOM 对上了。它也提醒我,资源审计不能只看“文件扩展名存在不存在”,还要看内容到底希望以什么形式呈现。
文档示例也要和实际资源一致
MarkdownPreview 页面是一个很容易被忽略的地方:它既有页面中实际展示的图片,也有写给读者看的 Markdown 示例代码。
如果只修展示图,不修示例代码,读者复制示例后仍然会得到旧的失效地址;如果只改代码块而忘了页面中的真实图片,浏览器还是会发送错误请求。因此这次把两者一起核对:将失效的 .webp 示例改为已经验证可用的同路径 .jpg,并同步修改实际展示与代码片段。
这不是一个大改动,但它体现了文档维护里很实用的一条原则:
当页面既展示结果又展示“如何写出这个结果”时,两个位置都属于用户可见的接口。
只修其中一边,实际上是在把问题留给下一位复制示例的人。
验证不止一次:静态审计和浏览器检查各管什么
修完引用后,如果只看编辑器里不再有红色波浪线,还是不够安心。因为主题、懒加载脚本、浏览器解析和服务端路径映射都可能让“源码看着对”与“实际请求正确”之间出现差异。
所以这次验证分成两层。
第一层:扫描生成后的 HTML 和 CSS
资源审计工具会读取构建产物,统计站内目标和直接外部媒体,并检查本地资源是否真的存在。它关注的不是某篇文章,而是生成目录中所有页面和样式表:
- HTML 的
src、href、poster; - 常见图片懒加载属性;
srcset中的候选资源;- 内联样式和 CSS 的
url(); - 相对路径、根路径和文章资源目录解析后的实际文件。
这一层最适合回答:“构建结果里还有多少地址指向不存在的本地文件?”
第二层:让浏览器真的打开代表页面
静态文件存在,也不代表浏览器一定不会请求旧 URL。比如主题脚本可能在运行后动态插入资源,onerror fallback 只有在图片加载失败时才会执行,编码后的文章路由也需要浏览器实际走一遍。
因此还增加了浏览器抽样验证:临时启动本地静态站点,为每个页面使用隔离的 Headless Chrome 实例,并记录网络事件和页面 DOM。覆盖的代表页包括:首页、友链页、两篇历史文章、包含视频资源的文章,以及 MarkdownPreview 页面。
检查项包括:
1 | 1. 页面 DOM 中是否还出现已修复的旧 URL; |
这两层并不重复。生成态审计负责广度,浏览器验证负责运行时路径;一个擅长找全站的遗留地址,另一个擅长发现“静态文本里看不出来”的实际请求。
最终同口径的数据:本地缺失资源归零了吗
为了避免把不同版本的扫描规则混在一起,下面只使用最终审计器口径下的前后结果。
| 指标 | 修复前 | 修复后 | 结果 |
|---|---|---|---|
| 生成 HTML 页面 | 182 | 182 | 页面数量保持不变 |
| 缺失的本地资源目标 | 16 | 0 | -16 |
| 缺失的本地资源引用 | 1,850 | 0 | -1,850 |
| 含错误 Mermaid 脚本请求的页面 | 178 | 0 | -178 |
| 外部失败/不可达媒体目标 | 12 | 9 | -3 |
| 外部失败/不可达媒体引用 | 369 | 9 | -360 |
这里最重要的一行是“缺失的本地资源目标”和“缺失的本地资源引用”都变成了 0。它表示在这次构建产物中,审计到的本地图片、脚本、样式和媒体目标都能在站内找到对应文件。
但这张表也有两点需要特别说明。
第一,外部失败/不可达媒体没有归零。这不是遗漏统计,而是主动保留的边界:部分旧正文原图已经无法从仓库和 Git 历史恢复,不能用无关内容替代;另有一个外部头像不能仅凭一次连接失败就下永久失效结论。
第二,审计器后续补充了更多 HTML 属性和内联样式的扫描。因此,不能把工具扩展前后的“总引用数”变化直接解释成网络负载涨跌;真正可比较的是同一套最终规则下的缺失资源统计。
浏览器抽样结果:旧地址真的没有再跑出来吗
静态审计完成后,六个代表页面在本地浏览器验证中的结果如下:
| 检查项 | 结果 |
|---|---|
| 已修复旧 URL 出现在 DOM 中 | 0 |
| 已修复旧 URL 被浏览器请求 | 0 |
| 本地资源 HTTP ≥400 | 0 |
| 浏览器报告的本地加载失败 | 0 |
这些数字能说明本次修复没有只停留在 Markdown 和 YAML 文件里:在代表页实际运行时,旧 fallback、错误 Mermaid 地址、旧头像路径和错误媒体路径没有重新出现。
不过它仍然只是抽样验证。第三方资源会受到网络、CDN、反爬策略和对方服务器变化影响;真实设备还会有不同浏览器内核、缓存和网络条件。所以更准确的说法是:已修复的本地请求在本次构建和代表页运行中通过验证,而外部资源问题仍会持续记录和观察。
这次治理留下的几个习惯
1. 资源正确性要从生成态看,而不是只从源码看
Hexo、主题和插件都会参与最终页面的形成。只搜索 Markdown 能找到一部分问题,却看不到主题配置展开后的资源、模板条件分支和浏览器运行时行为。
所以比较稳妥的流程应该是:
1 | 修改源码 → 构建站点 → 扫描生成 HTML/CSS → 浏览器抽样验证 |
这条链路不只适用于图片。字体、脚本、视频 poster、懒加载地址和 CSS 背景图,都可以沿着同一个思路检查。
2. 修复要有证据链,不要靠文件名猜测
“同名文件看起来像是它”不够,“这个地址已经确认存在并且内容类型匹配”才够。
这次能安全修复的路径,都是因为仓库里已经存在明确的同源 WebP、已有的文章资源文件,或者可验证的同路径替代格式。对于找不到原件的外部正文图,最诚实的做法不是假装解决,而是说明它暂时不可恢复。
3. 错误 fallback 不是兜底,而是二次失败
fallback 的意义是让一次资源失败仍有合理的展示结果。如果 fallback 自己也指向不存在的文件,那么它不但不能兜底,还会额外制造请求和排查噪音。
因此,默认图、错误图、友链头像默认值这种“平常不太显眼”的配置,反而值得纳入定期审计。
4. 外部链接不能因为难管就从报告里消失
外部资源天然不受本站控制,但“不受控制”不等于“不需要记录”。这次保留的外部遗留项没有被混进“本地资源已清零”的结论里,也没有为了让表格好看而被静默删除。
对内容型站点来说,透明地写明“还剩什么、为什么没改、下一步需要什么条件”,比给出一个不完整的全绿结果更有价值。
总结
这次无效请求优化没有依靠删内容或替换无关图片来换取一个漂亮数字,而是把资源问题分成了可以验证修复的本地部分,和需要保留证据、继续观察的外部部分:
- 关闭未使用的 Mermaid 配置,清除了 178 个页面中的错误 Mermaid 脚本请求;
- 修正默认图和错误 fallback,让历史 WebP 转换后的站内路径重新对齐;
- 修复友链、Markdown 标记和文章媒体类型,让数据、资源目录和最终 DOM 保持一致;
- 同步维护文档示例与实际展示,避免读者从示例中继续复制失效地址;
- 通过生成态审计和浏览器抽样双重验证,将最终同口径的本地缺失资源从 16 个目标、1,850 次引用降到 0;
- 保留外部遗留项的边界,不把暂时无法恢复的历史原图伪装成“已经修好”。
我觉得这次最有价值的收获,不是页面里少了多少条 404,而是给博客补上了一条更可靠的资源检查路径:以后改主题、迁移图片格式、整理文章资源,不能只看“页面有没有显示”,还要确认浏览器到底请求了什么。
本地可确认的问题已经收敛,外部资源的遗留也被明确记录。这样下一次继续整理图片、主题或部署环境时,至少不需要再从一堆模糊的控制台错误里重新猜起。🧩











