rel="preload"对动态路由脚本无效——因其无法在html解析阶段识别运行时决定的import()路径,强行使用会抢带宽、拖慢首屏;modulepreload同样不支持字符串拼接等动态导入场景;prefetch才是预取用户可能访问页面的正确选择。

rel="preload" 对动态路由脚本基本无效——它不解析 JS 内容,无法识别 import() 或路由配置里的路径,强行加只会抢带宽、拖慢首屏。
为什么 preload 不能用于动态路由脚本
动态路由脚本(比如 React Router 的 lazy(() => import('./Profile.js')) 或 Vue Router 的 component: () => import('@/views/Order.vue'))的加载路径是运行时决定的。浏览器在 HTML 解析阶段根本看不到这些 URL,rel="preload" 只能声明静态已知的资源。
常见错误现象:
- 写了
<link rel="preload" href="/js/Profile.js" as="script">,但实际模块路径是/js/Profile.js?ts=12345(构建工具加哈希),路径不一致导致缓存不命中 - 预加载了主 chunk,但没预加载其
import()依赖的子模块(如lodash-es或工具函数),结果仍要等第二次请求 - 在
里插入 preload 标签,或用 JS 动态创建,浏览器直接忽略
modulepreload 也只对静态 import 有效,不是万能解药
rel="modulepreload" 能递归抓取静态 import 依赖链,但它依然无法覆盖动态 import() 场景——因为 import('/pages/' + route.name + '.js') 这类字符串拼接,编译期不可分析。
真正起作用的前提:
- 路径必须完全一致:包括协议、域名、路径、查询参数(
?v=2.1.0和?v=2.1.1是两个资源) - 必须加
crossorigin属性,且和后续<script type="module"></script>的值严格一致(anonymous/use-credentials/ 空值) - 必须放在
里,且早于任何触发模块导入的脚本(比如早于router.start()或ReactDOM.render())
真正该用 prefetch 的地方,别硬套 preload
对“用户下一步可能点进来的页面”这类资源,rel="prefetch" 才是语义正确、行为可控的选择。它不抢首屏带宽,且支持路径模糊匹配(只要服务端返回 200,浏览器就缓存)。
实操建议:
- 在首页 HTML 的
末尾加:<link rel="prefetch" href="/profile.html" as="document">(预取整个页面) - 如果只预取 JS/CSS,写成:
<link rel="prefetch" href="/js/profile.chunk.js" as="script">,但注意不要加crossorigin(prefetch 不触发 CORS 预检,加了反而容易静默失败) - 配合
rel="preconnect"提前连 CDN 域名:<link rel="preconnect" href="https://cdn.example.com" crossorigin>,比瞎 preload 有用得多
构建时生成 preload 列表?小心路径和时机陷阱
有些工具(如 Webpack 的 PreloadPlugin 或 Vite 的 build.rollupOptions.output.manualChunks)会自动注入 preload。但要注意:
- 它们只对构建时可静态分析的
import()生效;若你用require(`./${name}.js`)或eval,插件完全无能为力 - 生成的
href必须是根相对路径(/assets/index.a1b2c3.js),不能是./index.js,否则浏览器解析失败 - 如果用了 code-splitting,多个 chunk 之间有依赖顺序,
modulepreload标签顺序错乱会导致部分模块无法复用解析结果
最易被忽略的一点:即使所有标签都写对了,只要页面初始 JS 里有一行 await import('./non-critical.js') 在首屏渲染前执行,它就会阻塞模块图建立,让前面的 modulepreload 白忙一场。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











