旧文章一夜全部失效,我三周后才知道:1877次「页面未找到」的排查实录
几个月没更新的博客,好不容易下定决心重新更新,结果刚升级完没多久,流量就莫名其妙掉了一大截。
本来没太当回事,直到发现后台近 2000 个请求(整整 1,877 次浏览)全都是 404「页面未找到」。抱着试一试的心态用 GA4 排查,没想到这玩意居然还挺强的,一层层维度拆下去,硬是把导致旧文章一夜之间全部失效的根本原因给揪了出来,确实有点意外。
下面记录一下这次完整的排查和定位过程。
1、期望目标
- 从 GA4 的「页面未找到」聚合报表中,拆解出全部真实的 404 失效 URL 路径和引荐来源。
- 查明为什么升级后全站旧文章的 URL 会一夜之间集体失效。
- 将排查出来的 730 条失效地址分类处理(恢复原链接 vs 301 重定向兜底),并建立 URL 冻结机制防止后续升级再次出现问题。
2、计划思考
- 现状问题:
- 静态博客(Hexo + Butterfly + Cloudflare Pages)平时极少宕机,构建全程正常、监控也是 200,但部分旧 URL 实际上已经 404,没有报警机制。
- GA4 默认按「网页标题 (Page title)」统计,Butterfly 主题把所有 404 页面统一显示为「页面未找到 | The IT Explorer」,几百个不同文章的 404 访问被合并成了一行,看不出具体是哪篇。
- 排查思路:
-
- 在 GA4 中通过次级维度和探索报表,把具体失效的网页路径和流量来源提取出来。
-
- 对比失效的旧 URL 和线上现有的 URL,看看规则差在哪里。
-
- 根据 404 爆发的时间节点查
git log,定位导致规则变更的代码或依赖。
- 根据 404 爆发的时间节点查
-
- 对所有失效路径进行分类治理(恢复原规则、301 重定向、归档兜底)。
-
3、操作步骤
3.1、静态博客为什么容易静默出错
简单来说,我们平时用的静态博客(比如 Hexo、Hugo 等),是把 Markdown 文件编译生成好静态 HTML 页面,然后部署到 Cloudflare Pages 或者服务器上。
这种模式不需要数据库,运行很轻量,但也有个局限:它平时几乎不会宕机,即便链接算错 404 了,构建依然是绿的,服务器也是正常的,所以很难及时察觉。
这次在 GA4 里面,最先看到的是整体访问流量的下滑异常:


打开「网页和屏幕」报表后,默认是按「网页标题」来聚合统计的。Butterfly 主题对于找不到的页面,统一渲染为「页面未找到 | The IT Explorer」。这就导致几百个不同文章的 404 访问,全部被合并到了同一行数据里:
| 实际发生的情况 | GA4 默认报表显示 | 产生的影响 |
|---|---|---|
| 几百个不同的文章链接返回 404 | 只有一行「页面未找到」 | 以为只是零星输错网址 |
| 每次 404 都是一次真实访问 | 1,877 次浏览堆在一起 | 看不到具体哪篇文章挂了 |
| 静态托管服务正常运行 | 构建与页面状态码正常 | 故障发生几周都没发现 |

要定位具体原因,第一步就是把报表拆解到具体路径。
3.2、如何在 GA4 中提取具体的失效链接
既然聚合的标题看不到具体网址,就需要利用 GA4 的维度拆解功能:
步骤 1:添加次级维度快速查看
在 GA4「网页和屏幕」标准报表中:
-
找到「页面未找到 | The IT Explorer」这一行;

-
点击第一列标题旁的
+(添加次级维度);

-
选择 「网页路径和屏幕类」(Page path and screen class)。

这样表格就会把每个触发 404 的具体 URL 展开列出来,按访问量排序。
步骤 2:通过探索报表导出完整列表
标准报表只能展开部分,如果需要导出整整几百条的完整清单进行逐条处理:
- 点击 GA 4 左侧菜单 「探索」(Explore) → 新建 「空白」 报表;


- 维度 (Dimensions):点击
+导入「网页标题」、「网页路径和查询字符串」;

- 指标 (Metrics):点击
+导入「浏览次数」;

-
配置行与值:把「网页路径和查询字符串」拖入 行 (Rows),把「浏览次数」拖入 值 (Values);
-
设置过滤器 (Filters):添加过滤规则 ——
网页标题包含页面未找到并点击应用。


通过这个方法,我导出了整整 730 条不同的失效路径。
步骤 3:查看流量来源
在探索报表中把维度换成 「页面引荐来源网址」(Page referrer) 或 「会话源/媒介」。


可以看到流量主要来自 Google 搜索和外部链接,说明之前积累的搜索引擎收录一直在返回 404,需要尽快恢复。
3.3、比对新旧 URL 找规律
拿到导出的 730 条路径后,把失效的旧链接和线上现存的链接放一起比对:
| 链接类型 | 实际路径示例 | 特征差异 |
|---|---|---|
| 失效的旧 URL(GA4 记录) | /posts/20260110-2026-nian-vps-ce-ping-jiao-ben-da-quan…/ |
汉字拼音音节之间有连字符 - |
| 线上的新 URL(当前生成) | /posts/20260110-2026nian-vpsceping-jiaobendaquan…/ |
汉字整体连拼,连字符消失了 |
从比对中可以明确几点:
- 文章都在,只是 URL 的生成规则变了;
- 汉字拼音音节之间的连字符(例如
2026-nian-vps-ce-ping变成了2026nian-vpsceping)全部没了; - 从时间线上看,这批 404 集中出现在 8 月 1 日 升级之后。
排查流程大致如下:
graph TD
A[1. 发现异常<br/>流量下滑,GA4 堆积 1,877 次 404] --> B[2. 拆解维度<br/>GA4 探索报表提取 730 条失效路径]
B --> C[3. 比对规律<br/>发现拼音音节间的连字符 '-' 消失]
C --> D[4. 确认时间<br/>404 集中爆发于 8 月 1 日升级后]
D --> E[5. 查看代码<br/>git log 定位 transliteration 依赖升级]
E --> F[6. 确认根因<br/>新版 slugify 改变了中文拼音分词规则]
F --> G[7. 分类处理<br/>恢复原规则原地复活 + 301 兜底 + URL 静态冻结]
3.4、为什么连字符会突然消失
有了时间节点和规律,去代码仓库查一下 8 月 1 日当天的提交:
1 | git log --oneline --since="2026-07-25" --until="2026-08-05" |
很快就定位到了一个提交,当时顺手把 transliteration 这个依赖库做了升级:
1 | - "transliteration": "1.6.6" |
根本原因说明:
博客的 Markdown 源码采用中文命名(如 20260110 2026年VPS测评脚本大全.md),Hexo 在每次编译生成时,会调用 transliteration 的 slugify 函数把中文转成拼音 URL。
- 1.6.6 版本:
slugify('2026年VPS测评')会分词并加入连字符,输出2026-nian-vps-ce-ping; - 2.6.1 版本:内部重构了逻辑,汉字与数字英文字符拼接时不再补连字符,直接输出
2026nian-vpsceping。
这就导致全站 151 篇文章(包含 20 个多语言版本)在重新构建后,旧的 URL 全部漂移失效。
另外,Hexo 生成文章路径时会直接使用文件名处理结果,即使在文章的 front-matter 里写了 slug,也容易被覆盖掉。
3.5、这 730 条失效路径如何分类处理
找到原因后,对导出的 730 条路径进行了分类,发现并不全是由这次升级引起的,还有部分历史遗留,一共归为 5 类:
| 分类 | 路径示例 | 数量 | 产生原因 | 处理方式 |
|---|---|---|---|---|
| 旧拼音格式文章 | /posts/20260110-2026-nian-vps-ce-ping…/ |
占比最大 | 8月1日依赖升级导致连字符丢失 | 恢复原生成规则(直接返回 200),保留 SEO 权重 |
| 早期数字短链 | /posts/39985、/{lang}/posts/{id}.html |
8 条 | 早期使用 abbrlink 插件生成的链接 | 单条配置 301 重定向至对应文章 |
| 多语言标签/分类变体 | /de/tags/Ubuntu、/ja/tags/VPS退款 |
约 79 条 | 多语言标签结构调整后的残留 | 301 重定向至当前语言的标准标签页 |
| 已下架语言目录 | /ms/*(马来语整站) |
约 42 条 | 曾经上线过但后续下架的语种 | 批量 301 规则通配跳转至英文对应页面 |
| 空归档月份 | /de/archives/2024/02 |
约 31 条 | 某个语言在某个月份没有文章 | 301 重定向至该语言归档总目录兜底 |
Cloudflare Pages _redirects 配置实现
在静态输出根目录下维护 _redirects 文件:
1 | # 1. 历史数字短链重定向 |
4、总结
其实做独立博客很容易陷入一种心态:越是不更新,就越不想更新。之前脑子里总想着搞个几十篇的宏伟写作计划 独立开发者工具箱:60篇实战指南 ,但只要中间停顿下来,就真的无限期搁置了。好不容易下定决心重新拾掇一下,结果一升级就踩到了这么深的一个坑。
写完的文章老实说平时根本不想回去看,毕竟写的时候太费脑子了。如果这次不是流量掉得太明显、鬼使神差去深挖了一下 GA4,可能这批 404 还会一直静默躺在后台。
这次排查留下的几个核心经验:
- URL 尽量做静态冻结:已发布文章的 URL 最好作为固定属性存储,不能完全依赖第三方转写库在构建时实时计算,避免依赖升级带来意外漂移。
- 升级依赖需比对 sitemap:
package-lock只能锁定版本,锁不住依赖包内部的算法行为变更。升级参与路由生成的库之后,最好对比一下前后的sitemap.xml。 - 能原地恢复的不用 301 将就:核心旧文章通过恢复原生成逻辑直接返回 200,能最大程度保住已有权重与访问体验。
- 定期查看 GA4 的 404 探索报表:静态博客缺少服务端告警,把「标题包含页面未找到」的探索报表保存下来,定期看一眼,能及早发现问题。
5、异常记录
1. Hexo Permalink 与 Slug 处理机制
在 Hexo 中,如果自定义了 URL 处理脚本,应在 hexo.extend.filter 的 before_post_render 阶段对已发布文章进行固化拦截:
1 | # _config.yml |
2. Cloudflare Pages 重定向配额注意项
- Cloudflare Pages 单个
_redirects文件最多支持 2,000 条静态规则 + 100 条动态通配规则(合计 2,100 条),文件体积上限为 100KB。 - 遇到大批量多语言 404 时,优先使用
:splat通配符做目录级收拢,避免逐条穷举写满配额。


