首页>前端>正文

前端资源预加载的优先级控制与降级实践

Feng 47 阅读 前端

首屏白屏时间压不下去,很多时候不是资源太大,而是加载顺序不对。浏览器拿到 HTML 后按解析顺序发现资源、排队下载,关键图片和字体往往排在后面。预加载要解决的就是这个排队问题:在解析阶段就告诉浏览器哪些东西重要,让它提前动手。

前端资源预加载的优先级控制与降级实践

先想清楚哪些资源值得提前拿

预加载不是越多越好,它本质上是抢带宽。判断标准很朴素:这个资源是否出现在首屏关键渲染路径上。首屏主图、首屏用到的 Web 字体、渲染阻塞的 CSS,属于典型候选。折叠线以下的图片、非首屏路由的 JS,提前拉只会挤占真正关键资源的通道。

有个电商站点的数据可以参考:只对首屏主图和字体做预加载,Lighthouse 性能分从 68 拉到 92,可交互时间缩短约四成。反过来,某门户站把全站 JS 一股脑预加载,首屏核心资源带宽被抢,LCP 反而恶化,后来重排优先级才恢复。两个案例的差别不在技术,在资源筛选。

link preload 的写法与两个常见坑

最直接的手段还是 <link rel="preload">。as 属性必须写对,它决定浏览器按什么类型处理请求、给什么优先级:

<link rel="preload" href="/img/hero-image.webp" as="image" type="image/webp">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

第一个坑是 as 写错。写成 as="image" 却指向一个脚本,浏览器会按图片优先级下载,真正用到时再按脚本重新请求一次,等于白拉。第二个坑是字体漏了 crossorigin。字体请求本身是 CORS 请求,预加载时不带 crossorigin,浏览器缓存里的那份和实际字体请求对不上,会触发重复下载,控制台还会报跨域相关告警。这个错误在内容型站点上很常见,排查时先看 Network 面板里同一 URL 是否出现两次。

优先级还能再细调。现代 Chromium 支持 fetchpriority 属性,配合 preload 使用可以压低或抬高某个资源的相对优先级:

<link rel="preload" href="/img/hero-image.webp" as="image" fetchpriority="high">

Chrome 117 起对 fetchpriority 的支持已经比较完整,但 Safari 和 Firefox 的实现节奏不同,把它当作锦上添花而非依赖项更安全。

服务端推送:能用,但要克制

HTTP/2 的 server push 让服务端在响应 HTML 时顺带把关键 CSS、runtime JS 推过去,省掉一轮 RTT。Nginx 配置大致是这样:

location / {
 http2_push /static/css/main.css;
 http2_push /static/js/runtime.js;
}

问题在于推送是盲目的。服务端不知道浏览器缓存里有没有这份资源,推了重复内容纯属浪费。某新闻站曾推了一堆非必要图片,移动端用户流量明显上涨,后来按用户行为数据收敛推送清单,跳出率降了 15%。更麻烦的是,Chrome 从 106 版本起已经移除对 HTTP/2 push 的支持,这条路的适用范围正在收窄。新项目不建议把它当主力方案,存量系统里保留可以,但要有替代预案。

按场景分流的动态预加载

静态声明解决不了”下一页要什么”的问题,这时候得上动态策略。三个信号源比较实用:Intersection Observer 判断元素是否接近视口,点击热力图推断用户下一步去向,Network Information API 区分网络类型。

某视频平台的做法值得借鉴:详情页里,Wi-Fi 用户额外预加载 720P 视频片段,蜂窝网络用户只预加载封面图。播放器启动速度提升五成,移动流量没有明显增长。核心思路是把预加载决策和网络条件绑定,而不是一刀切。

用 Speculation Rules 做页面级预渲染

原文提到的 Speculation Rules API 现在已经能在 Chromium 系浏览器里用了,Chromium 109 起支持 prerender 规则。它比资源级预加载更进一步,直接预渲染整个下一页:

<script type="speculationrules">
{
 "prerender": [{
 "source": "list",
 "urls": ["/product/detail"]
 }]
}
</script>

效果是用户点击时页面几乎瞬间呈现。但边界要清楚:Safari 和 Firefox 目前没有跟进,不支持的浏览器会直接忽略这段脚本,不会报错也不会降级。所以它只能作为增强层,基础体验仍要靠常规预加载兜底。另外预渲染会执行目标页的 JS,有副作用的后台请求要提前排除。

验证:别只看分数,看请求链

优化有没有生效,DevTools 的 Network 面板最直接。开优化前后各录一次,对比关键请求链的长度和时序,通常能缩短三到六成。WebPageTest 的多地对比测试里,常见的变化是 FCP 提前 300 到 500ms,TTFB 降两成左右。

更该关注的是反向指标。Chrome DevTools 控制台会对”预加载了但几秒内没被使用”的资源给出警告,Lighthouse 也有 unused preload 审计项。这两处出现条目,说明预加载清单需要瘦身。某 SaaS 平台就是靠这个信号逐步收敛,最后锁定 CSS、认证模块、首屏图表库三样,登录转化率提升 7%。

老浏览器的兜底

preload 在主流浏览器的支持率已经很高,但仍有环境需要降级。检测方式很简单,判断能否创建带 preload 的 link 元素,不支持就走异步脚本加载:

const preloadFallback = () => {
 if (!('preload' in document.createElement('link'))) {
 const script = document.createElement('script');
 script.src = 'critical.js';
 document.body.appendChild(script);
 }
};

实践上建议渐进推进:先上核心资源,跑 A/B 测试看真实用户指标,再逐步扩大范围。预加载的收益和风险都来自同一个地方——它改变了浏览器的资源调度顺序。理解这一点,比记住任何一条配置都管用。

ID:6938

Feng
这位作者很神秘,还没有填写简介。