2288 字
约 7 分钟
14
S14-A21 · 预加载的三个坑:`preload` 加了,首屏反而更慢

一个页面给"下一屏的图片"加了 <link rel="preload">,期望"滚动到时已经加载好"。上线后首屏 LCP 反而变差——因为预加载与首屏关键资源抢带宽,这个"有限几档"很关键:同优先级内按"发现顺序"排队,于是"预加载"改变的是"发现顺序"——它能让你想加载的东西更早,也能让关键资源更晚。
而浏览器的优先级只有"有限几档"。

预加载不是"提前加载就好",而是优先级竞争:带宽有限,你提前加载的东西,正在挤占"首屏真正需要"的那部分。


1. 三种"预加载"与它们的代价

手段 代价
rel=preload 立即开始下载,抢带宽
rel=prefetch 空闲时下载,低优先级
rel=preconnect 只建连(DNS/TCP/TLS),不下载

第一项最激进:它在 HTML 解析时就开始下载,与 CSS/JS 竞争第一项的"优先级"其实可以由 fetchpriority 调整:high 让它更早,low 让它退后——于是"preload + low"可以表达"提前但让路",而多数人只用了默认值。
——于是"你以为在加速,实际在插队"。

第二项的"不确定性"在于"空闲"的定义:有些浏览器把它放在"load 之后",有些放在"主线程空闲"——于是"我 prefetch 了但没生效"可能只是"一直没空闲"。
第二项是"空闲时":浏览器在空闲时才下载,因此它不会挤首屏——但"空闲"可能一直不出现。

第三项最安全:只提前建连(省 1–2 RTT),不占带宽,适合"确定要用但还不到时候"的第三方域名。

2. 坑一:与"关键资源"抢带宽

情形 结果
预加载大图(>200KB) 首屏字体/CSS 变慢
预加载多个资源 带宽被切碎
预加载低优先级资源 关键资源排队

第一项在"图片大"时最明显:图片预加载会与"字体文件"竞争第一项的"量化"方式是"看瀑布图里"关键资源的开始时间"":如果加了 preload 之后"字体开始时间"推后了,就是抢带宽的证据。
,而字体阻塞文字渲染——于是 LCP 变差。

修法是"只预加载"首屏需要但发现太晚"的资源":关键 CSS、首屏字体、LCP 图片——而不是"下一屏的东西"。

3. 坑二:as 属性写错等于没预加载

写法 结果
<link rel=preload href=x.js> 类型不匹配,可能被丢弃
<link rel=preload as=script href=x.js> 正确
<link rel=preload as=font href=x.woff2 crossorigin> 必须 crossorigin

第一项是"常见错误":缺 as 或写错,浏览器会"下载了但不用",于是"又一次请求"(浪费一次)。

第三项的"坑"在"隐式模式":同域字体可以不带 CORS,但很多字体托管在 CDN(跨域)——于是"本地好好的、线上白下"。
第三项的 crossorigin 是硬要求:字体请求必须是 CORS 模式,否则"预加载的那份"与"实际用的那份"不匹配,等于白下。

<!-- 三种正确的预加载 -->
<link rel="preload" as="font" type="font/woff2" href="/f.woff2" crossorigin>
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<link rel="preload" as="style" href="/critical.css">
<!-- 反例:缺 as / 字体缺 crossorigin → 下载了但复用不到 -->

"第三方域名"的处理最容易被忽略:你早知道会用某个域名,但不确定用哪个资源——此时 preconnect 是唯一正解。

<!-- 三种"对第三方"的准备方式 -->
<link rel="dns-prefetch" href="//cdn.example.com">          <!-- 只解析 DNS -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>   <!-- 建连(省 1–2 RTT) -->
<link rel="preload" as="image" href="https://cdn.../a.webp">         <!-- 直接下资源(占带宽) -->
<!-- 建议:首屏后才用到的第三方 → preconnect;首屏就要 → preload -->

4. 坑三:预加载了"用不到的东西"

情形 后果
移动端也预加载桌面大图 浪费流量与带宽
预加载"可能点击"的页面 大概率不点
预加载后又改了 URL 双份下载

第一项要用"媒体查询"限制:**media="(min-width: 1024px)"**第一项的"收益"很直接:移动端少下几百 KB,而它只需要一个属性——所以"预加载清单"里应当给每个资源标"哪个断点需要"。
,于是移动端不下载。

第三项的"防法"是"预加载与引用共用同一个 URL 常量"(构建时注入)——而"手写两处"几乎必然会不一致。
第三项最坑:预加载的是 /a.js,而实际引用的是 /a.js?v=2——两次下载,且"预加载的那份"白费。

"预加载错配"可以用一段两行的检查脚本在 CI 里发现,而不必等控制台警告:

// 检查:HTML 里的 preload href 是否都被实际引用
grep -oE 'rel="preload"[^>]*href="[^"]+"' index.html | grep -oE 'href="[^"]+"' | cut -d'"' -f2 > preloaded.txt
grep -oE '(src|href)="[^"]+\.(js|css|woff2|webp)"' index.html | grep -oE '"[^"]+"' | tr -d '"' | sort -u > used.txt
comm -23 <(sort preloaded.txt) used.txt    # 预加载了但没引用 → 候选删掉

5. 正确的预加载清单

资源 是否预加载
LCP 图片 是(fetchpriority=high)
首屏字体 是(带 crossorigin)
关键 CSS 是(若被 JS 引入而发现晚)
下一屏图片 否(用 loading=lazy)
可能点击的页面 否(用 prefetch)
第三方域名 preconnect

这张表的逻辑是"只提前加载"首屏确实需要、但浏览器发现太晚"的东西这个"发现太晚"的判据可以量化:在瀑布图里,这个资源的"开始时间"是否明显晚于"它被需要的时间"。晚,才值得 preload。
——其余的交给别人(lazy、prefetch、preconnect)。

"优先级"要显式写,因为它决定了"谁先开始":

<!-- 三个显式优先级:关键图片抢前,次要资源让路 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<link rel="preload" as="image" href="/below-1.webp" fetchpriority="low">
<img src="/hero.webp" fetchpriority="high" alt="">   <!-- 与 preload 同一个 URL -->
<!-- 关键:preload 与 img 必须同 URL,否则两份下载 -->

6. 排错:五种"预加载没效果/更差"

现象 成因 处理方向
LCP 变差 抢了关键资源带宽 减少预加载
控制台"未使用预加载" as/crossorigin 不匹配 修正属性
双份下载 URL 不一致(带版本号) 对齐 URL
移动端流量涨 未加 media 限制 加媒体查询
预加载了但仍慢 服务器/网络是瓶颈 见"带宽与延迟"

第五项(预加载了但仍慢)的"分流"很重要:如果"服务器响应慢"或"网络差",预加载只会让"排队更早"——它不解决"总量"问题。
第二项最直接:浏览器会在控制台提示"资源被预加载但未在几秒内使用"——看到这条就说明"预加载写错了或时机不对"。

最后一个"验收"方式:对比"加预加载前后"的 LCP 与"关键资源开始时间"。而"只看 LCP"不够——因为 LCP 可能"没变差但也没变好",而瀑布图能看出"资源顺序变了"。

// 三个验收指标(同设备、同网络条件)
LCP                  应下降或不变(变差 → 抢带宽)
关键字体开始时间      应提前(不变 → 预加载没生效)
预加载未使用警告数     应为 0(>0 → as/crossorigin/URL 错配)

7. 边界:什么情况不必预加载

场景 为什么 做法
首屏资源已足够早 无"发现晚"问题 不预加载
页面很小(< 10 个请求) 竞争不激烈 不必要
资源在 HTML 里直接引用 已被发现 不必要
移动端弱网为主 预加载浪费配额 谨慎

判断口径一句话:这个资源"是不是首屏需要、而且浏览器发现得太晚"。最后一条与"演进"有关的边界:预加载是"补丁",不是"架构"。它修的是"发现太晚",而"关键资源太多"应当靠"减少资源"解决——两者的方向不同。
不是(下一屏、可选),就不要 preload;是(LCP 图、字体、被 JS 引入的关键 CSS),才值得。


8. 自测

  1. 三种预加载手段的代价分别是什么?为什么 preload 会"插队"?
  2. 为什么"预加载大图"会让 LCP 变差?它抢了谁的带宽?
  3. as 写错会怎样?字体为什么必须加 crossorigin?
  4. "双份下载"是怎么发生的?怎么避免?
  5. "预加载清单"的判据是什么?为什么"下一屏图片"不该 preload?
  6. 判断"是否需要预加载"的那个问题是什么?
S14-A21 · 预加载的三个坑:`preload` 加了,首屏反而更慢
https://www.clxhxhhr.top/posts/6593/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。