Issue 编号:86
创建:2026-07-31T06:33:07Z
更新:2026-10-12T03:23:35Z
状态:open
标签:["blog:publish", "javascript", "笔记", "前端", "博客开发", "cloudflare", "astro", "AI 写作为主"]
正文
最近博客里出现了一个很奇怪的问题:Mermaid 流程图没有渲染,页面直接显示了图表源码。
一开始我怀疑过 Mermaid 语法、前端构建产物和浏览器缓存。但检查线上 DOM 后发现,页面里能够找到
.mermaid-diagram 容器,却没有生成任何 SVG,也没有进入渲染失败的回退状态。这意味着问题甚至还没有走到 Mermaid 的解析阶段——负责初始化 Mermaid 的 JavaScript 根本没有正常执行。继续检查浏览器实际收到的 HTML,才发现 Cloudflare Rocket Loader 改写了 Astro 生成的模块脚本。关闭 Rocket Loader 后,两张流程图立即恢复,DOM 中重新出现了两个 SVG。
这次故障让我重新认识了 Rocket Loader:它不是一个单纯的“JavaScript 加速开关”,而是一套会改变脚本加载时序的优化机制。
Rocket Loader 是做什么的?
Rocket Loader 是 Cloudflare 提供的一项前端性能优化功能。
它要解决的问题是:浏览器解析 HTML 时,普通 JavaScript 脚本可能会阻塞后续内容的解析和渲染。如果页面引用了很多脚本,或者某个第三方脚本响应很慢,用户可能需要盯着空白页面等待。
Rocket Loader 的策略是让内容先出现,再执行 JavaScript。
按照 Cloudflare 的说明,它会延迟页面中的 JavaScript,让文字、图片和字体等内容优先渲染,同时异步处理内联脚本与外部脚本,并尽量维持原来的执行顺序。
因此,它优化的主要是“用户什么时候能看到内容”,而不是 JavaScript 本身的运行速度。
可以把这种取舍简单理解为:
用户可能更早看到正文,但依赖 JavaScript 的按钮、图表和其他交互要稍后才能使用。
它是如何实现的?
Rocket Loader 同时依赖 Cloudflare 边缘节点和浏览器端运行时代码。
请求经过 Cloudflare 时,Cloudflare 会在边缘修改返回给浏览器的 HTML。它暂时把原有脚本处理成浏览器不会立即执行的形式,让浏览器先继续解析页面。随后,Cloudflare 注入的 Rocket Loader 运行时再恢复这些脚本,并模拟普通脚本、
async 和 defer 等不同执行方式。整个过程可以概括为:
这里最重要的一点是:Rocket Loader 会接管原本由浏览器负责的脚本加载时序。
这也是它既能改善首屏速度,又可能造成兼容问题的原因。
它和 async、defer 有什么区别?
async 和 defer 是浏览器原生支持的脚本属性。async会并行下载脚本,下载完成后尽快执行,多个脚本之间不保证执行顺序。
defer会并行下载脚本,等 HTML 解析完成后按照文档顺序执行。
- Rocket Loader 会在 Cloudflare 边缘改写页面,然后通过自己的运行时延迟并调度多个脚本。
如果能够修改应用代码,通常应该先使用浏览器原生机制、ES modules、代码拆分和按需加载。它们的行为更明确,也更容易由构建工具和测试覆盖。
Rocket Loader 更像是站点之外的一层统一优化:不改源代码,也能尝试推迟大量脚本。但多了一层调度,就多了一层需要验证的行为。
哪些场景适合使用?
Rocket Loader 比较适合内容优先、交互较轻的传统网站,例如:
- 正文阅读是页面的主要目的;
- 页面包含多个同步或第三方脚本;
- 慢速脚本明显阻塞了首屏内容;
- 暂时无法调整源站模板或构建流程;
- 允许正文先显示、交互稍后可用;
- 有性能监控和功能回归测试。
例如,一个资讯站的正文被广告、统计和推荐脚本挡住时,Rocket Loader 可能让读者更快看到文章。
不过,“可能更快”必须通过数据证明。开启前后至少应该比较 FCP、LCP、TBT 或 INP,同时确认按钮、表单、埋点和第三方组件仍然正常。
哪些场景不适合?
如果 JavaScript 本身就是产品的核心,而不只是页面增强,使用 Rocket Loader 就需要格外谨慎。
风险较高的场景包括:
- React、Vue、Astro 等由构建工具管理模块加载和 hydration 的应用;
- 后台管理系统、编辑器和实时协作应用;
- 登录、支付、下单等关键流程;
- 依赖严格执行顺序或生命周期事件的脚本;
- 使用严格 CSP、SRI 或复杂第三方依赖的页面;
- 已经使用 ES modules、
defer、代码拆分和按需加载的站点;
- 缺少端到端测试,无法及时发现静默失效。
这类页面即使 HTML 和 CSS 正常显示,也可能只有局部功能失效。相比直接白屏,这种“看起来没问题”的故障反而更难发现。
为什么它影响了 Astro 和 Mermaid?
Astro 会把组件脚本构建成
type="module",并负责 npm 依赖打包、TypeScript 转换、去重和代码拆分。Mermaid 的初始化链路大致是:
这次故障中,HTML 和 CSS 都正常返回,Mermaid 的源码容器也存在,但模块入口没有成功完成初始化。于是后面的动态加载和 SVG 生成都没有发生。
最终表现就是:读者看到的不是流程图,而是一整段
graph LR 源码。关闭 Rocket Loader 后,Astro 模块恢复由浏览器原生机制加载,两张 Mermaid 图都成功生成了 SVG。
出现问题时如何排查?
第一步不是立即改代码,而是做开关对照。
- 找到一个能够稳定复现问题的 URL。
- 记录当前页面的 DOM、Console 和 Network 状态。
- 临时关闭 Rocket Loader。
- 使用无痕窗口或清理缓存后重新访问。
- 比较入口脚本、依赖请求和最终 DOM。
还可以直接检查线上 HTML:
浏览器里则应检查:
- JavaScript 入口有没有发出请求;
- 入口的动态依赖有没有继续加载;
- Console 是否出现 CSP、MIME 或模块加载错误;
- 目标组件有没有完成初始化;
- 最终结果是否真的存在,而不是只看 HTTP 200。
对于 Mermaid,最直接的验收条件就是:
构建成功只能证明产物生成了,不能证明经过 CDN 改写后仍能在真实浏览器中运行。
如果只想排除部分脚本怎么办?
Cloudflare 支持使用
data-cfasync="false" 排除单个脚本:但它有几个限制:
- 属性必须直接存在于初始 HTML 中;
data-cfasync必须写在src前面;
- 如果脚本依赖其他脚本,依赖链也需要一并排除;
- 其他脚本仍然会被 Rocket Loader 接管。
对于 Astro 还多一层问题:Astro 默认只处理没有额外属性的组件脚本。直接添加
data-cfasync 可能让脚本退出 Astro 的打包和 TypeScript 处理管线。因此,现代框架项目出现全局兼容问题时,直接关闭 Rocket Loader,或者使用 Cloudflare Configuration Rule 按主机名和路径关闭,通常比逐个排除构建产物更可靠。
是否应该开启 Rocket Loader?
可以用下面这组问题判断:
Rocket Loader 不是一个“开启就会更快”的按钮。它实际上是在用脚本执行时序的变化,交换更早的内容绘制。
如果站点主要依赖 HTML 和 CSS,JavaScript 只是锦上添花,这笔交换可能值得。如果页面由模块脚本驱动,或者 JavaScript 承担核心功能,那么框架、浏览器原生加载机制和应用自身的性能优化通常更可控。
最后
这次 Mermaid 故障提醒了我:线上页面并不只由仓库里的代码决定。
代码经过构建、部署、CDN 缓存和边缘优化后,浏览器真正收到的内容可能已经发生变化。排查前端问题时,不能只看源代码和构建结果,还要观察最终 HTML、网络请求和真实 DOM。
性能优化也不应该以“某个开关是否开启”为目标。真正的目标应该是:
用户更早看到有用内容,同时页面仍然完整可用。
如果速度提升的代价是流程图、按钮或关键交互静默失效,那么它就不是优化。
