resource hints 怎么用:preload、preconnect、dns-prefetch 各管什么,别一把梭

resource hints 是让浏览器在发现资源之前就知道它的那几行 <link>。这篇讲五种提示各管什么、一张能直接照抄的决策表,以及一条命令查出你页面上已经发了哪些。

改写与发布6 分钟读完1137 次阅读
resource hints 怎么用:preload、preconnect、dns-prefetch 各管什么,别一把梭

resource hints 是写在 <link> 上的一组提示,作用是让浏览器在它自己发现某个资源之前就提前知道。值得记的只有五种:dns-prefetchpreconnectpreloadmodulepreloadprefetch。它们各自换来的东西不一样,加错的那种照样占带宽,什么都没换来。

动手之前

你需要的是一张首屏依赖了「浏览器发现得晚」的资源的页面:写在样式表里的字体、写在 CSS 里的背景图、被另一个脚本 import 进来的脚本。如果首屏要用的东西本来就写在 HTML 里,加提示没有用,因为浏览器早就看见了。

数字那一半已经有人量过。resource hints 实测数的是多少个首页各发了多少条提示,阻塞渲染的 CSS 实测量的是拖慢首屏的那些样式表。这篇补的是另一半:哪种提示用在哪、放在哪,以及怎么查自己现在发了什么。

资源提示不是「到处加一点」的优化项。它是少数几个决定:让浏览器先动哪几样东西。

五种 resource hints 各换来什么

按「你想让什么提前」来选。提示能做的事分三档:开一条连接、下一个文件、把一个模块准备好,代价也随这三档往上升。

提示做什么用在哪
dns-prefetch只做该源的 DNS 解析一堆第三方源,成本要低
preconnectDNS 加 TCP 加 TLS,连到源站关键路径上那两三个源
preload以更高优先级下载一个资源字体、CSS、发现得晚的图
modulepreload抓取、解析并编译一个模块入口模块那一个
prefetch为下一次跳转低优先级预取下一个页面,不是这一页

MDN 对 preconnect 的定义是:这是在告诉浏览器「用户很可能要用这个源上的资源」,于是浏览器可以提前把连接建起来。同一页里还有一句常被跳过的限定:同源请求上它没有任何收益,因为那条连接本来就是开着的。

为什么 preload 和那两个连接提示不是一回事

连接类提示是建议,preload 是硬指令。MDN 与 Google 自己那篇 preload 文章把这条线画得一样:preconnectprefetch 由浏览器自己决定执不执行,而 preload 是强制的。它会真的发起一次更高优先级的抓取;页面最后没用上这个文件,Chrome 会在 load 之后大约 3 秒在控制台里报一条「未使用的 preload」。这条警告就是写错的代价。

preload 值得用的场景是「浏览器发现得晚」的资源,不是本来就写在 HTML 里的资源。用 @font-face 写在样式表里的字体是最标准的例子:浏览器必须先下载并解析那份样式表,才知道有这个字体文件。写在 CSS 里的背景图同理。preload 让下载和样式表并行,而不是排在它后面。

有一个属性不能省。as 告诉浏览器这次抓的是哪一类资源,它决定了优先级、请求头和缓存键。少写 as 的 preload,在部分浏览器里会被抓两次,因为这次预加载和后面真正的请求对不上。

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/css/critical.css" as="style" />
<link rel="modulepreload" href="/js/app.js" />

按这个顺序做

五步,每一步都有一个能当场验的完成标志。在你熟悉的页面上跑一遍,一小时内能做完。

  1. 先把发现得晚的资源列出来。字体、CSS 背景图、脚本 import 进来的东西都算。完成标志:列表里每一项都是浏览器在初始 HTML 里看不到的。
  2. 先定源站类提示。关键路径上的第三方源用 preconnect,长尾的用 dns-prefetch。完成标志:拿到 preconnect 的源不超过三四个。
  3. 只 preload 首屏要用的。每个发现得晚的关键资源一行 preload,都带上 as。完成标志:没有任何一条 preload 指向首屏根本不渲染的东西。
  4. 只给入口模块加 modulepreload它比 preload 多做了解析与编译两步。完成标志:入口模块被覆盖,依赖留给浏览器自己发现。
  5. 一周后回头看一遍。提示会改变请求顺序,帮了一种版面的顺序可能害了另一种。完成标志:控制台没有未使用 preload 的警告,首屏仍然正常。

交付物:一张决策表和一条自查命令

把这张表留着。它把整套方法压在一屏里,也是你以后会回来翻的那部分。

情形用什么别做
一个关键的第三方源preconnect给每个源都 preconnect
十个第三方源,都不关键dns-prefetch给十个源都花 TLS 时间
首屏要用的字体preloadas="font"漏掉 ascrossorigin
单独成文件的关键 CSSpreloadas="style"把整个包都 preload
模块加载的应用外壳modulepreload每个 chunk 都 modulepreload
用户接下来要开的页面prefetch在慢网络上还 prefetch

下面这条命令读的是一个线上页面已经发了哪些提示。想找出「preload 指错了东西」或者「preconnect 到一个页面上根本没人用的源」,这是最快的办法。

curl -s -L --compressed -A "Mozilla/5.0" https://example.com/ -o page.html

python3 -c "
import re
html = open('page.html').read()
head = html.split('</head>')[0]
for m in re.finditer(r'<link\b[^>]*>', head):
    t = m.group(0)
    if re.search(r'rel=[\"\\']?[^\"\\'>]*(preload|preconnect|dns-prefetch|prefetch|modulepreload)', t):
        print(re.sub(r'\s+', ' ', t))
"

拿输出对着上面那张表读。preconnect 到一个页面上再没出现过的源,就是白开的。preload 少了 as,是 bug。preconnect 超过四行,通常说明这一页什么都想提前,结果什么都没排上优先级。

做错了会怎样,怎么发现

坏掉的提示配置基本逃不出三种。每一种都只有在你看请求顺序而不是看标记的时候才露出来。

  1. preconnect 开太多。MDN 明写:需要连很多第三方域时把它们全 preconnect 会适得其反,这个提示最好只留给最关键的连接。症状是你加完之后首屏并没有变快。
  2. preload 了一个页面没用上的文件。它以高优先级下完,然后躺在缓存里。Chrome 会在 load 后约 3 秒在控制台报出来;把这条警告当缺陷处理,别当噪音。
  3. preload 漏了 as浏览器对不上真正的请求,于是抓两次,第二次才是你想要改优先级的那个。

有一条边界要先说清楚。我们没测加载时间,标记本身也看不出一个 preconnect 的源站到底有没有被用到。preconnect 只在那个源真的被早早用到时才有意义,preload 只在那个资源真的在关键路径上时才有意义。唯一诚实的测试,是在你自己的页面上、把网络限速之后做一次前后对照。

常见问题

preconnect 开几个算多?

MDN 没给数字,它取决于页面。实际的上限是首屏真正需要的源的数量,通常三四个。其余的放进 dns-prefetch,它便宜到可以铺得宽一些。

字体要不要全都 preload?

不要。只 preload 首屏实际渲染的那一个。把所有字重和子集都 preload,会和访客真正在等的东西抢带宽,没用上的那些还会触发和别的 preload 一样的控制台警告。

preload 会执行脚本或者应用样式吗?

不会。它只是以更高优先级下载并缓存。执行和应用仍然发生在页面读到使用它的那个元素时 —— 这正是 preload 能把下载提前、却不改变代码何时运行的原因。

prefetch 和给下一页做的 preload 是一回事吗?

接近,但不是。prefetch 面向的是将来的某次跳转,优先级比 preload 更低。MDN 还提到浏览器现在普遍做了缓存分区,这让 prefetch 对别的顶级站点无效,所以它只适合你自己站内的页面。

资源提示会影响排名吗?

这里没有任何关于排名的说法,几份来源也都没有这么写。提示改变的是资源什么时候被抓,这是一个加载问题。能说的更窄:提示加在错的资源上,只花带宽,不换来东西。

多少个首页真的发了这些提示、又有几个一条都没发,数字在 resource hints 实测里。想看一个不渲染的客户端从同一个页面拿到什么,QueryWin 是怎么工作的是这件事的产品那一侧。

本文属于 QueryWin 实操手册 · 第 2 阶

resource hints 怎么用:preload、preconnect、dns-prefetch 各管什么,别一把梭