DNS解析是浏览器访问任何域名前的必经步骤。当页面中引用了第三方脚本、字体、图片或统计服务时,浏览器需要逐一对这些域名进行解析,而每次解析都可能耗费几十到一百多毫秒。如果这些解析发生在关键渲染路径上,用户看到页面内容的时间就会被明显推迟。DNS预解析(DNS Prefetching)正是为了解决这个问题而生:它让浏览器提前完成域名到IP地址的转换,把这部分耗时从用户感知的关键路径中剥离出去。

先弄清楚DNS解析为什么会拖慢页面
要理解预解析的价值,得先看看浏览器请求一个资源的完整流程。以加载一张托管在CDN上的图片为例,浏览器大致要经历这样几个阶段:DNS解析把域名转换成IP地址,TCP三次握手建立连接,如果是HTTPS还要完成TLS协商,最后才是发送HTTP请求和接收响应。整个过程像一条流水线,而DNS解析是流水线的第一站。
问题在于,DNS解析并不总是快的。浏览器通常会查询本地缓存,缓存未命中则询问操作系统的hosts文件和系统缓存,仍无结果时才会向运营商或公共DNS服务器发起递归查询。在移动网络环境下,这个查询往返可能超过200毫秒。更糟糕的是,浏览器对并发DNS查询的数量有限制,当页面引用了大量不同域名的资源时,解析请求会排队等待,延迟成倍放大。
一个典型场景是:页面首屏已经渲染完成,但用户点击按钮后需要加载第三方登录组件,此时浏览器才开始解析登录服务的域名,用户会明显感到卡顿。如果解析工作早已在后台完成,点击后的响应速度会立刻改善。这就是预解析的核心收益——把不可避免的开销提前到用户等待感较弱的时段。
DNS预解析的用法与原理解析
DNS预解析的使用方式非常简单,只需要在HTML的head部分添加一个link标签,并指定rel属性为dns-prefetch。标准写法如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>DNS预解析示例</title>
<!-- 预解析第三方域名 -->
<link rel="dns-prefetch" href="https://cdn.example-site.com">
<link rel="dns-prefetch" href="https://analytics.example-site.com">
<link rel="dns-prefetch" href="https://fonts.example-site.com">
</head>
<body>
<!-- 页面内容 -->
</body>
</html></code>需要特别注意的是,按照HTML规范,href属性中的链接末尾不应该带斜杠。也就是说<link rel="dns-prefetch" href="https://cdn.example-site.com">是推荐写法,而末尾多加一个斜杠虽然多数浏览器也能容错处理,但不符合规范。另外,link标签必须放在head中,并且要尽量靠前,这样浏览器解析到它时能尽早触发解析动作。
它的工作原理可以概括为:浏览器解析HTML时遇到dns-prefetch指令,会把这个域名加入待解析队列,在网络线程空闲时(比如正在下载其他资源、解析器等待数据时)并行执行DNS查询。查询结果会写入浏览器缓存,等到后续真正发起对该域名的请求时,直接命中缓存,省去整个解析等待。换句话说,预解析并没有消灭DNS解析的耗时,只是把它挪到了一个不阻塞用户操作的时间窗口里。
还有一点容易被忽略:浏览器自身也具备一定的自动预解析能力。当页面中存在真实的链接或表单时,Chrome等浏览器会根据启发式算法自动预解析其中出现的域名。但自动机制只对已解析到的URL生效,对于通过JavaScript动态拼出的域名、或用户交互后才触发的第三方资源,浏览器无法提前预判,这类场景才是手动声明dns-prefetch最能发挥价值的地方。
dns-prefetch与preconnect的区别及组合策略
在实际优化中,dns-prefetch常常和preconnect一起出现,两者的区别值得仔细区分。dns-prefetch只提前完成DNS解析这一步;而preconnect更进一步,会提前完成DNS解析、TCP握手和TLS协商三个阶段,等于把整个连接建立过程都搬到了后台。可以用一个表格直观对比:
| 特性 | dns-prefetch | preconnect |
|---|---|---|
| 提前完成的阶段 | DNS解析 | DNS解析、TCP握手、TLS协商 |
| 资源开销 | 低,仅域名查询 | 较高,需维持连接约10秒 |
| 适用场景 | 不确定何时使用的域名 | 确定马上要请求的关键域名 |
| 滥用风险 | 较小 | 较大,会占用连接资源 |
从表格可以看出,preconnect虽然收益更大,但成本也更高。浏览器会为每个preconnect的域名保持一条打开的连接,如果页面对十几个域名都做了preconnect,而实际只请求了其中两三个,那么其余连接纯属浪费,甚至可能挤占其他请求的连接配额。因此实践中推荐一个组合策略:对最关键的一两个第三方域名使用preconnect,比如字体服务、首屏必需的API接口;对可能用到但优先级较低的域名使用dns-prefetch,比如统计上报、延迟加载的图片域名。
两者组合的典型写法如下,同时兼顾了旧版浏览器的降级兼容:
<!-- 关键域名:提前建立完整连接 --> <link rel="preconnect" href="https://api.example-site.com"> <!-- 非关键域名:仅做DNS预解析 --> <link rel="dns-prefetch" href="https://cdn.example-site.com"> <link rel="dns-prefetch" href="https://static.example-site.com">
这里有一个实用技巧:把dns-prefetch写在preconnect后面可以作为不支持preconnect的旧浏览器的降级方案。因为支持preconnect的浏览器遇到后面的dns-prefetch时,发现域名已经建立了连接,会自动忽略;而老浏览器不认识preconnect,则至少能执行DNS预解析,形成一层合理的兜底。
实施时的注意事项与验证方法
预解析虽好,但也要遵循几个原则。第一,只预解析页面真正会用到的域名。有些文章建议把常见的第三方域名一股脑全加上,这种做法弊大于利——每个预解析请求本身也是一次网络查询,对根本不会访问的域名做解析纯属浪费流量和电量,在移动设备上尤其明显。一般建议预解析的域名控制在4到6个以内,且都应该有明确的业务用途。
第二,注意浏览器对预解析的控制开关。Chrome允许用户或系统策略关闭预解析功能,HTTPS页面默认可以预解析,但如果目标域名存在安全策略限制,预解析可能被跳过。另外,服务端可以通过响应头中的X-DNS-Prefetch-Control来控制页面内的预解析行为,例如设置为off可以整体禁用。开发者遇到预解析不生效时,可以检查是否被这类策略拦截。
第三,学会用工具验证效果。Chrome开发者工具的Network面板中,勾选 DNS lookup 列可以看到每个请求的DNS耗时;在地址栏输入chrome://net-export可以导出网络日志,观察预解析查询是否真的发生。如果使用Lighthouse做性能审计,报告中 Opportunities 区域会直接提示哪些第三方域名缺少preconnect建议,这是发现优化点的好入口。
最后要强调,DNS预解析只是性能优化链条上的一环,它解决的是连接建立阶段的开销,对服务器响应慢、资源体积大、渲染阻塞等问题无能为力。正确的做法是把它和资源压缩、懒加载、HTTP/2多路复用等手段配合使用,形成完整的优化体系。在大多数项目中,加上几个link标签的成本几乎为零,而针对第三方资源较多的页面,收益往往能立即体现在首屏指标上,值得作为基础优化项落实到每一个页面模板中。