rel="preload"能提前触发主Bundle下载,因其在<head>解析阶段即发起请求,与HTML解析并行;而<script>需等解析到标签才请求,延迟400–800ms。

SPA 的主 Bundle 必须用 rel="preload",不能用 <script src="main.js"> 直接引入——否则首屏 JS 下载会晚 400–800ms,且无法抢占网络空闲窗口。
为什么 rel="preload" 能提前触发主 Bundle 下载
浏览器解析 HTML 时,遇到 <script type="module"> 或 import('./app.js') 才发起请求,此时 HTML 解析已过半,网络带宽可能被其他资源占用。而 <link rel="preload" href="main.js" as="script"> 在 <head> 解析阶段就触发下载,与 HTML 解析并行,不阻塞也不依赖 DOM 构建顺序。
- 必须放在
<head>最早位置,早于所有<script>标签 - 漏写
as="script"会导致 Priority 显示为Low,等同于普通 fetch,起不到加速作用 - 如果后续用
type="module"加载,且带integrity,则 preload 标签也必须带完全相同的integrity值,否则缓存不复用 - 服务端返回的 MIME 类型必须是
application/javascript,否则 Safari/Edge 可能拒绝执行
rel="preload" 和 rel="modulepreload" 别混用
如果你的主入口是 ES 模块(type="module"),且内部有静态 import 链(比如 import { render } from './core.js'),只用 rel="preload" 是不够的:它只下载 main.js,子模块仍要等解析后才请求,多轮 RTT 延迟明显。
- 改用
<link rel="modulepreload" href="main.js">,它会预解析main.js并递归预取所有静态import的依赖 -
modulepreload不允许加as属性,加了会被忽略 -
href必须和import()或type="module"中写的路径完全一致(包括查询参数,如?v=1.2) - 多个
modulepreload标签之间无顺序依赖,但必须全部出现在首个模块脚本之前
构建产物路径变动会导致 preload 失效
Webpack/Vite 默认给 JS 文件加 hash(如 app.a1b2c3.js),每次构建路径都变,导致 rel="preload" 缓存失效、重复下载。
立即学习“前端免费学习笔记(深入)”;
- 对首屏关键 Bundle,建议禁用 hash 或固定文件名(如
main.js),或通过构建插件将真实路径注入 HTML 模板 - 若用 Vite,可通过
build.rollupOptions.output.entryFileNames控制输出名;Webpack 可配output.filename+HtmlWebpackPlugin动态注入 - 避免在 HTML 中硬编码带 hash 的路径,否则上线后 preload 标签指向 404
别用 rel="preload" 做跨页面预加载
rel="preload" 只对当前页有效,页面跳转后预加载即终止,缓存也不跨会话复用。想预加载下一页资源,必须用 rel="prefetch"。
-
prefetch请求的Initiator是prefetch,Priority 为Low,不会抢首屏带宽 - 不要在首页
preload所有路由 chunk——Lighthouse 评分暴跌,首屏反而更慢 - 验证是否生效:跳转到下一页后,目标资源应显示
200 (from memory cache)或304,而非重新请求
最容易被忽略的是 integrity 一致性与 MIME 类型校验——这两点出错时,preload 看似成功,实际后续 script 标签仍会重发请求,白忙一场。



















