spa主bundle必须用rel="preload"而非script标签,因其可在html解析阶段并行下载、抢占网络空闲窗口,提前400–800ms起始下载;而script标签会阻塞解析且无法控制优先级,且必须配as="script"、路径一致、避免重复请求。

大型单页应用(SPA)的首屏 Bundle 必须用 rel="preload",而不是直接写 <script src="main.js"></script> —— 否则会错过 HTML 解析阶段的网络空闲窗口,实测延迟下载 400–800ms。
为什么 SPA 主 Bundle 不能用 script 标签直接引入
浏览器遇到 <script src="main.js"></script> 会立即阻塞 HTML 解析,等脚本下载、编译、执行完才能继续;而现代 SPA(如 Vite 或 Webpack 构建的 React/Vue 应用)通常靠 type="module" 或动态 import() 启动,这意味着 main.js 的请求要等到解析完所有 HTML、遇到模块脚本标签或 JS 执行到 import 行才发起——此时首屏已卡住几百毫秒。
rel="preload" 则不同:它在 解析早期就触发下载,与 HTML 解析并行,且不执行、不阻塞。关键点在于:它只负责“拉下来”,后续仍需一个真实的 <script></script> 标签来执行。
- 必须放在
最前位置,早于任何<script></script> - 不能省略
as="script",否则 Chrome 里 Priority 显示为 Low,和普通 fetch 一样低优 - 路径必须和最终执行时的
src完全一致(含 hash、查询参数),否则重复请求 - 若用了
integrity,preload 和 script 标签都得带相同值,否则缓存不复用
as="script" 是硬性要求,不是可选项
漏写 as="script" 或写成 as="fetch"、as="module",浏览器不会把它当高优脚本处理。Network 面板里能看到 Priority 是 Low,甚至可能被降级为 idle 级别;更严重的是,后续 <script src="main.js"></script> 仍会重新发一次请求,造成重复下载。
尤其注意:as="module" 是无效的 —— rel="preload" 不支持 module 类型语义,只认 as="script"。如果主入口是 ES 模块,且依赖静态 import 链(比如 import { render } from './core.js'),那应该改用 rel="modulepreload",而不是硬套 as="module"。
-
rel="modulepreload"不需要as属性,加了反而被忽略 - 它会预解析 main.js 并递归预取所有静态 import 的依赖(如 core.js、router.js)
- 多个
modulepreload标签无顺序依赖,但必须全部出现在首个模块脚本之前
容易被忽略的 MIME 类型与缓存复用问题
服务端返回的 Content-Type 必须是 application/javascript,否则 Safari 和部分旧版 Chrome 会拒绝执行,即使 preload 成功下载也白搭。
缓存方面,preload 资源进的是当前页的 script 缓存池,不跨会话复用。也就是说,A 页面写了 preload,B 页面不会自动拿到缓存;B 页面要用,就得自己也声明一遍。这跟 rel="prefetch" 的行为完全不同 —— 后者专为跨页设计,但优先级低、时机不可控。
- Webpack/Vite 构建后路径带 hash?确保 hash 稳定,否则每次构建都失效 preload 缓存
- CDN 缓存策略要配合:对
main.[hash].js设置长缓存(max-age=31536000),但对 HTML 文件禁用强缓存(避免 preload href 过期) - 不要在 HTML 里同时写
rel="preload"和<script defer></script>却让它们指向不同路径(比如一个带?v=1.2,一个没带),这是重复请求高发区
真正难的不是写对一行 <link rel="preload" href="main.js" as="script">,而是确保它在整个构建、部署、缓存链路中始终指向同一个可执行资源,并且浏览器真的把它当高优任务跑 —— 稍有偏差,就从加速变成拖慢。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











