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

resource hints 是写在 <link> 上的一组提示,作用是让浏览器在它自己发现某个资源之前就提前知道。值得记的只有五种:dns-prefetch、preconnect、preload、modulepreload、prefetch。它们各自换来的东西不一样,加错的那种照样占带宽,什么都没换来。
动手之前
你需要的是一张首屏依赖了「浏览器发现得晚」的资源的页面:写在样式表里的字体、写在 CSS 里的背景图、被另一个脚本 import 进来的脚本。如果首屏要用的东西本来就写在 HTML 里,加提示没有用,因为浏览器早就看见了。
数字那一半已经有人量过。resource hints 实测数的是多少个首页各发了多少条提示,阻塞渲染的 CSS 实测量的是拖慢首屏的那些样式表。这篇补的是另一半:哪种提示用在哪、放在哪,以及怎么查自己现在发了什么。
资源提示不是「到处加一点」的优化项。它是少数几个决定:让浏览器先动哪几样东西。
五种 resource hints 各换来什么
按「你想让什么提前」来选。提示能做的事分三档:开一条连接、下一个文件、把一个模块准备好,代价也随这三档往上升。
| 提示 | 做什么 | 用在哪 |
|---|---|---|
dns-prefetch | 只做该源的 DNS 解析 | 一堆第三方源,成本要低 |
preconnect | DNS 加 TCP 加 TLS,连到源站 | 关键路径上那两三个源 |
preload | 以更高优先级下载一个资源 | 字体、CSS、发现得晚的图 |
modulepreload | 抓取、解析并编译一个模块 | 入口模块那一个 |
prefetch | 为下一次跳转低优先级预取 | 下一个页面,不是这一页 |
MDN 对 preconnect 的定义是:这是在告诉浏览器「用户很可能要用这个源上的资源」,于是浏览器可以提前把连接建起来。同一页里还有一句常被跳过的限定:同源请求上它没有任何收益,因为那条连接本来就是开着的。
为什么 preload 和那两个连接提示不是一回事
连接类提示是建议,preload 是硬指令。MDN 与 Google 自己那篇 preload 文章把这条线画得一样:preconnect 和 prefetch 由浏览器自己决定执不执行,而 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" />
按这个顺序做
五步,每一步都有一个能当场验的完成标志。在你熟悉的页面上跑一遍,一小时内能做完。
- 先把发现得晚的资源列出来。字体、CSS 背景图、脚本 import 进来的东西都算。完成标志:列表里每一项都是浏览器在初始 HTML 里看不到的。
- 先定源站类提示。关键路径上的第三方源用
preconnect,长尾的用dns-prefetch。完成标志:拿到preconnect的源不超过三四个。 - 只 preload 首屏要用的。每个发现得晚的关键资源一行
preload,都带上as。完成标志:没有任何一条 preload 指向首屏根本不渲染的东西。 - 只给入口模块加
modulepreload。它比preload多做了解析与编译两步。完成标志:入口模块被覆盖,依赖留给浏览器自己发现。 - 一周后回头看一遍。提示会改变请求顺序,帮了一种版面的顺序可能害了另一种。完成标志:控制台没有未使用 preload 的警告,首屏仍然正常。
交付物:一张决策表和一条自查命令
把这张表留着。它把整套方法压在一屏里,也是你以后会回来翻的那部分。
| 情形 | 用什么 | 别做 |
|---|---|---|
| 一个关键的第三方源 | preconnect | 给每个源都 preconnect |
| 十个第三方源,都不关键 | dns-prefetch | 给十个源都花 TLS 时间 |
| 首屏要用的字体 | preload 加 as="font" | 漏掉 as 或 crossorigin |
| 单独成文件的关键 CSS | preload 加 as="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 超过四行,通常说明这一页什么都想提前,结果什么都没排上优先级。
做错了会怎样,怎么发现
坏掉的提示配置基本逃不出三种。每一种都只有在你看请求顺序而不是看标记的时候才露出来。
- preconnect 开太多。MDN 明写:需要连很多第三方域时把它们全 preconnect 会适得其反,这个提示最好只留给最关键的连接。症状是你加完之后首屏并没有变快。
- preload 了一个页面没用上的文件。它以高优先级下完,然后躺在缓存里。Chrome 会在 load 后约 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 阶


