看完这篇,你能分清浏览器给每个资源排的优先级,并用 preload、fetchpriority、modulepreload 把首屏关键资源提前拉到队列前面,同时避开 preload 最常见的几个坑。
第一步:先看浏览器默认给了什么优先级
打开 DevTools → Network 面板,右键表头勾上 Priority 列,刷新页面。你会看到每个请求都标着 Highest / High / Medium / Low / Lowest。
默认规则大致是:
- HTML 文档、CSS、同步
<script>:High 及以上
- 字体(
@font-face 触发):High,但发现得晚——要等 CSS 下载解析完才开始下
<img> 首屏图片:Medium 或 Low
- 通过 JS 动态插入的资源:Low
preload 的价值就在这里:那些「晚才发现」的关键资源(CSS 里的背景图、字体、深层 JS 模块),用 preload 提前声明,浏览器一解析到 <head> 就开始下载。
第二步:写对 preload 的基本语法
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
三个属性缺一不可:
as:告诉浏览器这是什么类型的资源,决定它的优先级和请求头 Accept。可选值有 script、style、font、image、fetch、document、audio、video、track。
type:资源的 MIME 类型,浏览器不支持时会跳过下载,避免白下。
crossorigin:跨域资源必须加,字体尤其必须。
注意:字体没写 crossorigin 时,浏览器会先下载一次(匿名模式不匹配),到真正用字体时再下载一次,结果同一文件下载两遍,反而更慢。这是 preload 最经典的翻车方式。
第三步:区分「该预载」和「不该预载」
值得预载的:
- CSS 里引用的字体、背景图(浏览器要等 CSS 解析完才发现它们)
- 关键的 JS 模块(首屏交互依赖的)
- 图片轮播的第一张、LCP 大图
不值得预载的:
- 首屏 HTML 里就直接出现的
<img>、<script>——浏览器解析到就已经在下了,preload 只是重复声明
- 首屏之外的图片、组件库、埋点脚本
注意:preload 不是免费的。它会抢占带宽,优先级高过普通资源。Chrome 会在控制台警告「resource was preloaded but not used within a few seconds」——看到这条警告,说明你预载了用不上的东西,删掉它。
第四步:用 fetchpriority 微调优先级
as 只能决定大致档位,真正细调靠 fetchpriority:
<!-- 抬升 LCP 大图 -->
<img src="/hero.avif" fetchpriority="high" alt="...">
<!-- 降级首屏以下的图片 -->
<img src="/below.jpg" fetchpriority="low" loading="lazy" alt="...">
<!-- preload 的图片默认低优先级,可以抬 -->
<link rel="preload" href="/hero.avif" as="image" fetchpriority="high">
as="image" 的 preload 在 Chromium 里默认是 Low 优先级,如果你预载的是 LCP 图却没加 fetchpriority="high",效果可能还不如不写。
响应式图片预载要带 imagesrcset / imagesizes,否则浏览器没法匹配你实际会选哪张:
<link rel="preload" as="image"
href="/hero-800.avif"
imagesrcset="/hero-800.avif 800w, /hero-1600.avif 1600w"
imagesizes="100vw">
注意:imagesrcset 的兼容性不如基础 preload 好,老浏览器会退化成只下 href 那一张。如果你的站点覆盖这些环境,慎重使用。
第五步:ES 模块用 modulepreload
如果是 <script type="module"> 的入口,别用 rel="preload" as="script",用 modulepreload:
<link rel="modulepreload" href="/js/main.js">
<link rel="modulepreload" href="/js/chart.js">
它会下载 + 解析 + 编译,并把模块放进模块映射表,后续 import 直接命中,省掉「下载入口 → 发现依赖 → 再下载」的瀑布。较老的 Safari 不支持,但不会报错,只是退化成普通请求。
第六步:验证,别凭感觉
改完做三件事:
- Network 面板看 Priority 列,关键资源是否真的提前到了 High;看瀑布图起点是否上移。
- 翻控制台,清掉所有 preload 未使用警告。
- 跑一次 Lighthouse 的「预加载关键请求」审计,确认它不再提示你预载某些资源。
注意:preload 只解决「发现太晚」,不解决「资源太大」。字体子集化、图片压缩、代码分割该做的还得做,否则只是把慢提前了。
小结
preload 是给晚发现的关键资源用的,不是给所有资源用的。
as 必填且要写对,字体必须加 crossorigin,否则双下载。
as="image" 默认低优先级,配 fetchpriority="high" 才有意义。
- ES 模块用
modulepreload,不要用 as="script"。
- 看到「preloaded but not used」警告,直接删掉对应的 preload。
- 改完用 DevTools 的 Priority 列和 Lighthouse 复核,不要靠猜。