Skip to content

前端 DNS 优化

基于 HTTP 现代标准 · 核于 2026-06

速查

  • DNS 是首字节前的隐藏延迟:每个新域名首次访问都要走一遍 DNS 解析(约 20~120ms),它发生在 TCP/TLS 握手之前,直接拉长资源到达的时间,且不在你的 JS 代码里、不易察觉。
  • <link rel="dns-prefetch">:提前把某个域名解析成 IP,省掉将来请求时的那段 DNS 查询时间。只做 DNS 这一步,开销极小,对跨域资源域名才有意义。
  • <link rel="preconnect">:更进一步——把 DNS + TCP 握手 + TLS 握手全部提前完成(HTTP 站只到 TCP),将来请求直接复用这条暖连接。能省 100~500ms,但更耗资源。
  • 何时用哪个preconnect 用于马上、确定要用的关键域名,且数量要克制;dns-prefetch 用于可能、稍后会用到、或域名较多时的广撒网。
  • preconnect 必须克制:浏览器会在约 10 秒内关闭未被使用的预连接;预连接太多会挤占带宽、拖慢真正关键的资源。一般只对 2~4 个最关键的第三方源用。
  • crossorigin 决定预连接是否「白做」:以匿名模式抓取的资源(最典型是字体 @font-face)走的是带 CORS 的独立连接,preconnect 不加 crossorigin 会连错连接,等于只省了 DNS。
  • 回退写法(同域名同时上两条)preconnect + dns-prefetch 各写一行——支持 preconnect 的浏览器走全连接,不支持的至少吃到 DNS 预解析。
  • 绝不要合并成一个 relrel="preconnect dns-prefetch" 会触发 Safari 的 bug 取消掉 preconnect,务必拆成两条 <link>
  • 同源不用提示preconnect/dns-prefetch同源资源无收益(连接早已建立或正在建立),只对第三方/跨域域名有效。
  • 域名收敛:减少页面用到的不同域名数,就是减少要解析/握手的次数——能合并到自有 CDN 的资源尽量收敛,比逐个加提示更治本。
  • 资源提示家族dns-prefetch < preconnect < preload/prefetch——前两者只「热身连接」,后两者才真正「下载内容」,按确定性与代价从轻到重选用。
  • HTTP 头也能下发Link: <https://cdn.example.com>; rel=preconnect<link> 等效,可在响应头里更早地给出提示。

A. DNS 解析为什么是前端性能问题

打开一个页面,浏览器拿到 HTML 后会去请求里面引用的各种资源——脚本、样式、图片、字体、接口数据。对其中每一个新出现的域名,在真正建立连接、发出请求之前,必须先把域名解析成 IP 地址。这一步就是 DNS 查询。

它的特点是「隐形且靠前」:DNS 解析排在 TCP 三次握手、TLS 握手之前,是访问新域名的第一道关卡(没解析出 IP 后面什么都做不了);它不在你的业务代码里、性能面板上也易被忽略,但每次约 20~120ms 实打实加在首字节(TTFB)之前;而现代页面常引用十几个不同域名(统计、广告、字体、图床、CDN……),首次访问每个都各付一次 DNS 代价。

text
访问一个新的 https 域名,连接前要经历:

  DNS 解析  →  TCP 三次握手  →  TLS 握手  →  发送请求 → 首字节
  (20~120ms)   (1 个 RTT)      (1~2 RTT)
  └─ dns-prefetch 提前的就是这一段 ─┘
  └────────── preconnect 提前的是这三段 ──────────┘

为什么不能只靠浏览器自己优化

浏览器确实会缓存 DNS、复用连接,但它只有在解析到 HTML 里的资源引用、或脚本动态发起请求那一刻才知道要连哪个域名。资源提示(resource hints)的价值在于:让你比浏览器更早告诉它「待会儿要用这个域名」,把解析/握手提前到「HTML 还在下载、JS 还没执行」的空档里并行完成。

本页不讲缓存有效期与安全

DNS 解析结果能缓存多久(TTL)属于上一页 DNS 缓存与 TTL;加密 DNS(DoH/DoT)与 DNS 层安全属于下一页 DoH/DoT 与 DNS 安全。本页只聚焦前端如何用资源提示和域名策略压低 DNS 带来的延迟

dns-prefetch 是一条提示(hint):告诉浏览器「用户接下来很可能要用到这个源的资源,请先把它的域名解析了」。浏览器随即在后台抢先完成这个域名的 DNS 解析,将来真正请求该域名资源时,就省掉了那段解析时间。

html
<head>
  <!-- 提前解析这些第三方域名 -->
  <link rel="dns-prefetch" href="https://fonts.gstatic.com" />
  <link rel="dns-prefetch" href="https://cdn.example.com" />
  <link rel="dns-prefetch" href="https://api.analytics.com" />
</head>

它的定位是「轻量、广撒网」:

  • 只做 DNS 这一步,不碰 TCP/TLS,开销极小,多写几条也不太心疼。
  • 适合「可能用到」的域名:那些不一定马上请求、但稍后大概率会触达的第三方源(如埋点、社交分享、可能加载的图床)。
  • 只对跨域有意义:同源域名浏览器早已在解析/连接,提示是多余的。

浏览器支持与降级

MDN 标注 dns-prefetchBaseline 2025(2025 年 9 月起在主流浏览器新版本中广泛可用)。它本质是「提示」,老浏览器不识别时只是忽略这行 <link>不会报错、不会破坏页面,可以放心写——拿不到优化但也没有副作用。

preconnectdns-prefetch 更进一步:它让浏览器对目标源提前建立完整连接——

  • HTTP 站点:DNS 解析 + TCP 三次握手;
  • HTTPS 站点:DNS 解析 + TCP 三次握手 + TLS 握手。

将来真正请求该源资源时,这条「暖连接」已经就绪,能省下整段连接建立时间(实测可快 100~500ms),效果比单纯 dns-prefetch 明显得多。

html
<head>
  <!-- 马上要从这个 CDN 取关键资源,提前把连接建好 -->
  <link rel="preconnect" href="https://cdn.example.com" />
  <!-- 字体走匿名 CORS 抓取,必须带 crossorigin -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
</head>

它的定位是「重、精准、克制」:

  • 代价更高:占用一条真实连接(含 TLS 协商的计算与往返),不是免费的。
  • 只用于「马上、确定」要用的关键源:例如承载首屏关键 CSS、字体、首图的 CDN,或必然会调的核心接口域名。
  • 数量必须克制:浏览器会在约 10 秒内关掉一直没被使用的预连接;预连接铺太多反而挤占带宽、延迟真正关键的资源。经验上只对 2~4 个最关键的第三方源预连接。

crossorigin 漏了等于白连

这是 preconnect 最常见的坑。以匿名模式(anonymous / CORS)抓取的资源——最典型的就是 @font-face 加载的 Web 字体——会用一条带 CORS 标记的独立连接。如果 preconnect 没加 crossorigin,你提前建好的是「非 CORS 连接」,字体请求用不上它、得另起一条新连接,结果只省下了 DNS、TCP/TLS 全白做

html
<!-- 对字体源:必须加 crossorigin,否则预连接对字体无效 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

判断原则:该源上要抓的资源是否以 CORS/匿名模式请求(字体、fetch()/XHR 默认跨域、带 crossorigin 的脚本与图片),是就加 crossorigin

D. dns-prefetch vs preconnect:怎么选

二者是同一思路的轻重两档dns-prefetch 只热身 DNS,preconnect 把整条连接热身好。

维度dns-prefetchpreconnect
提前完成的工作DNS 解析DNS + TCP + TLS(HTTP 站到 TCP 为止)
节省时间量级~20~120ms(DNS 部分)~100~500ms(整段连接建立)
资源开销极小,几乎免费较大,占用真实连接 + TLS 计算
适用场景可能、稍后会用到的域名马上、确定要用的关键域名
数量建议可以多写(广撒网)严格克制(2~4 个最关键源)
是否需要 crossorigin否(只做 DNS)抓 CORS/匿名资源(字体等)时需要
浏览器支持Baseline 2025Baseline 2020(更早、更广)

选择口诀

  • 这个域名确定马上要用、且是关键路径(首屏 CSS/字体/首图的 CDN、核心 API)→ 用 preconnect
  • 这个域名只是有可能用到、或一次要覆盖很多个(埋点、第三方挂件、备选图床)→ 用 dns-prefetch
  • 不确定时优先 dns-prefetch:它代价小、副作用低;preconnect 留给真正算得清收益的关键源。

回退写法:同一域名两条都上,但绝不合并

想兼顾「新浏览器吃满 preconnect、老浏览器至少吃到 dns-prefetch」,对同一域名分别写两行

html
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />

支持 preconnect 的浏览器走全连接、忽略 dns-prefetch;不支持的回退到 DNS 预解析。

切忌把两个关键字塞进同一个 rel

html
<!-- 反例:会触发 Safari 的 bug,导致 preconnect 被取消 -->
<link rel="preconnect dns-prefetch" href="https://cdn.example.com" />

E. 域名收敛与资源提示家族

域名收敛:从源头减少解析次数

资源提示是在「既定的多域名」前提下抢时间;域名收敛则是直接减少域名数量,更治本:

  • 页面引用的不同域名越少,需要解析/握手的次数就越少,也越少触发资源提示的管理负担。
  • 能合并到自有 CDN / 同一子域的静态资源尽量收敛;第三方脚本能自托管的考虑自托管。
  • 注意权衡:HTTP/2 之后域名分片(domain sharding)反而是反模式——把资源拆到多个子域会破坏连接复用、增加 DNS 与握手成本,与收敛背道而驰。

收敛之后,提示更值钱

域名收敛与资源提示不冲突而是互补:先把域名收敛到少数几个关键源,再对这几个关键源精准 preconnect——少而准,正好契合 preconnect「数量必须克制」的要求。

资源提示家族:从「热身连接」到「下载内容」

dns-prefetch/preconnect 属于一组 资源提示(resource hints),按「确定性 + 代价」从轻到重排:

关键字做什么何时用
dns-prefetch只解析 DNS可能用到的跨域域名,广撒网
preconnectDNS + TCP + TLS 建连接确定马上要用的少数关键源
preload真正下载当前页高优先级资源本页必用、但发现得晚的资源(字体、关键 CSS/JS)
prefetch低优先级下载将来要用的资源下一步导航很可能用到的资源,空闲时拉取

一句话区分:前两者只「热连接」、不下载字节preload/prefetch 才真正把资源下载下来。本页聚焦的 DNS 优化只涉及前两者;preload/prefetch 的取舍属于资源加载优化的范畴。

用 HTTP 响应头下发提示

资源提示不止能写在 <link> 里,也能用 HTTP 响应头 Link 给出,让浏览器在解析 HTML 之前就拿到提示、更早动手:

http
Link: <https://cdn.example.com>; rel=preconnect
Link: <https://fonts.gstatic.com>; rel=preconnect; crossorigin

小结

  • DNS 是首字节前的隐形延迟:每个新域名首次访问都要解析(~20~120ms),排在 TCP/TLS 之前,逐域名累加,是值得前端主动优化的一环。
  • dns-prefetch = 只提前解析 DNS:轻量、几乎免费、可广撒网,适合「可能、稍后」用到的跨域域名;老浏览器忽略而无副作用。
  • preconnect = 提前建好整条连接(DNS+TCP+TLS):省 100~500ms 但更耗资源,只用于「马上、确定」的关键源,数量克制(2~4 个),未用连接约 10 秒被回收。
  • 两个高频坑:抓字体等 CORS/匿名资源的 preconnect 必须加 crossorigin(否则白连);同域名回退要写两条 <link>绝不能合并成 rel="preconnect dns-prefetch"(Safari bug)。
  • 治本靠域名收敛:减少不同域名数比逐个加提示更根本,且 HTTP/2 后域名分片是反模式;资源提示家族按代价从轻到重为 dns-prefetch < preconnect < preload/prefetch

解析结果能缓存多久、TTL 如何影响这些优化的命中,见上一页 DNS 缓存与 TTL;DNS 查询本身的加密与安全(DoH/DoT、防劫持)见下一页 DoH/DoT 与 DNS 安全