modulepreload 是专为 es 模块设计的预加载机制,只提前 fetch 不执行,能加速首屏关键模块加载,但前提是 href 与 import 路径完全一致(含协议、域名、路径、查询参数),否则静默失效;对动态 import() 无效,不递归预加载子模块,需手动声明所有依赖。

modulepreload 是什么,它真能加速模块加载?
rel="modulepreload" 不是魔法,它只是让浏览器提前发起模块脚本的 fetch 请求,不执行、不解析,只预加载。真正起效的前提是:你用 import 加载的模块路径必须和 link 中的 href 完全一致(包括协议、域名、路径、查询参数),否则浏览器不会复用预加载结果。
- 预加载发生在 HTML 解析阶段,早于 JS 执行,对首屏关键模块(如路由入口、UI 组件库)有实际收益
- 对动态
import()的模块无效——浏览器无法静态预判路径 - 不会触发模块执行,所以不能替代
rel="preload"+as="script"用于非 ES 模块
怎么写正确的 modulepreload link 标签?
关键不是“加了就行”,而是路径匹配必须严格。比如你的主模块是:
import { init } from './src/main.mjs';
对应 link 必须写成:
<link rel="modulepreload" href="./src/main.mjs">
- 路径必须是相对或绝对 URL,不能是包名(如
href="vue"无效) - 如果使用构建工具(Vite、Webpack),注意输出文件名带哈希,
href必须同步更新,否则预加载失效 - 多个模块可并列写多个
link,但不要滥用——预加载太多会挤占正常资源带宽
常见错误:为什么写了 modulepreload 却没效果?
最常踩的坑是路径不一致。浏览器控制台 Network 标签页里能看到 modulepreload 请求状态,但若后续 import 发起的是另一个 URL(哪怕只差一个 / 或查询参数),就不会复用。
-
import './main.js'和<link href="./main.mjs">→ 不匹配(扩展名不同) -
import '@/utils.js'(经过别名解析)→ 实际路径是/src/utils.js,link必须写后者,不能写@/utils.js - 使用
import('./chunk-${id}.js')动态导入 → 无法预加载,link写死路径也没用 - 在 HTTP/2 下,多个
modulepreload可能被合并传输,但不保证顺序,依赖关系仍由 JS 执行时 resolve
modulepreload 和 preload、prefetch 怎么选?
三者用途完全不同,混用反而拖慢:
-
rel="preload"+as="script":适合普通 script(非模块)、CSS、字体等,可指定onload回调,但不处理模块依赖图 -
rel="prefetch":低优先级,用于将来可能用到的资源(如下一页面的模块),不适用于当前页面关键路径 -
rel="modulepreload":唯一能正确预加载 ES 模块及其静态依赖链的机制,但仅限已知路径的顶层模块
真正需要关注的不是“要不要加”,而是“哪些模块的路径稳定、确定、关键”。比如一个 SSR 应用的客户端 hydration 入口,或者微前端中主应用明确加载的子模块入口,才值得加 modulepreload。
路径一致性是硬门槛,其他优化都建立在这之上。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











