preconnect 的正确使用姿势与性能提升实战

百里正浩 优化 阅读 2,900
赞 16 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

我一般在项目里用 preconnect 的时候,直接写在 <head> 里,简单粗暴但有效。先上代码:

preconnect 的正确使用姿势与性能提升实战

<link rel="preconnect" href="https://jztheme.com" crossorigin>
<link rel="preconnect" href="https://cdn.jztheme.com">

注意两点:一是带 crossorigin 的情况,二是不带的。如果目标域名需要跨域请求(比如字体、API、CDN 资源),那就必须加 crossorigin,不然浏览器会忽略这个 preconnect 提示。

举个例子,我之前做个项目,从 https://cdn.jztheme.com 加载字体,结果字体加载慢得要死。折腾了半天发现是 preconnect 没加 crossorigin,浏览器压根没提前建立连接。加上之后,首屏性能提升了将近 300ms,这在移动端很关键。

还有一点,别一股脑全加上。我见过有人把所有外链都 preconnect,连 Google Analytics、Facebook Pixel 都加,结果页面卡顿更严重。为啥?因为 DNS 查询和 TCP 握手也是有开销的,尤其在低速网络下,太多并发连接反而拖累主线程。

所以我的做法是:只对一定会用到、且延迟敏感的第三方资源加 preconnect。比如 CDN、核心 API 域名、WebFont 服务器。其他的一律不碰。

这几种错误写法,别再踩坑了

说说我见过的几个典型错误,我自己也踩过。

错误一:漏掉 crossorigin,结果白搭

<!-- 错了!跨域资源没加 crossorigin -->
<link rel="preconnect" href="https://fonts.googleapis.com">

这种写法在 Chrome DevTools 里看 Network,你会发现它根本没提前发起连接。因为浏览器认为这是跨域请求,而你没声明意图,干脆当没看见。正确写法:

<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>

哪怕目标服务器不返回 CORS 头,这个属性也得加上,至少告诉浏览器“我打算跨域访问”。

错误二:对 HTTP/2 同域名资源滥用 preconnect

比如你已经用 https://cdn.jztheme.com 加载了 main.js,又在这个域名下加载 style.css、utils.js 等一堆资源。这时候你再加 preconnect 是多余的。

HTTP/2 支持多路复用,一个连接能跑多个请求。你提前 preconnect 并不会提升性能,反而浪费一次 TCP + TLS 握手。我之前为了“保险起见”加了,结果 Lighthouse 直接警告:“Avoid multiple connections to the same domain”。

错误三:把 preload 和 preconnect 搞混

<!-- 错了!这不是 preconnect 的用法 -->
<link rel="preconnect" href="https://jztheme.com/api/data.json">

这是典型的混淆。你想预加载数据?那应该是 preloadfetch 打头的资源提示。而 preconnect 只负责建立连接,不负责传输数据。

更离谱的是还有人这么写:

<link rel="preconnect" href="/static/main.js">

本地资源你连啥 connect?路径都在同一域名下,浏览器自己会复用连接。这种写法纯属画蛇添足,还会被工具报 warning。

实际项目中的坑

我在一个电商项目里遇到个诡异问题:页面在 iOS Safari 上首次打开特别慢,Android 和桌面端都正常。排查半天发现是 WebFont 加载延迟导致的。

我们用了 https://fonts.jztheme.com 提供的自定义字体,也加了 preconnect,但 Safari 就是不提前连。后来查资料发现:Safari 对 preconnect 的支持比较保守,尤其是遇到 crossorigin 时,如果后续没有明确的资源请求(比如 @font-face),它可能就不会触发。

解决方案是:确保 preconnect 后面紧跟 preload 字体,或者至少有一个 link[rel=stylesheet] 指向那个域名。这样 Safari 才会认真对待你的连接提示。

另一个坑是动态加载场景。比如用户点击按钮才加载某个组件,这个组件依赖 https://api.jztheme.com 的数据。这时候你在 <head> 里早就 preconnect 了,但页面初始加载时并不需要这个 API,白白占用了连接资源。

我的处理方式是:用 JavaScript 动态插入 preconnect。比如在用户 hover 按钮时就提前连:

function prefetchApiConnection() {
  const link = document.createElement('link');
  link.rel = 'preconnect';
  link.href = 'https://api.jztheme.com';
  document.head.appendChild(link);
}

// 用户 hover 时触发
document.getElementById('load-data-btn').addEventListener('mouseenter', prefetchApiConnection);

虽然不能保证连接一定维持住(浏览器可能会回收空闲连接),但至少提高了命中率。实测下来,接口响应时间平均减少了 150ms 左右。

还有一点很多人忽略:preconnect 不是银弹。它只能优化连接建立阶段,对 DNS 查询、TLS 握手有帮助,但如果目标服务器本身慢,或者网络差,该卡还是卡。我之前优化一个项目,preconnect 全加上了,Lighthouse 分数涨了,但真实用户体验提升不明显。最后发现是后端接口太慢,前端再怎么优化也救不了。

要不要加 dns-prefetch?

有些老项目还在用 dns-prefetch,像这样:

<link rel="dns-prefetch" href="https://jztheme.com">

其实现在没必要。现代浏览器中,preconnect 已经包含了 DNS 解析的功能,甚至还多了 TCP 和 TLS 的准备。你再加一层 dns-prefetch,纯属重复劳动。

不过兼容性上要注意:IE 完全不支持 preconnect,老版本 Android Browser 也不行。如果你还得兼容这些古董,可以保留 dns-prefetch 作为降级方案:

<link rel="preconnect" href="https://jztheme.com" crossorigin>
<link rel="dns-prefetch" href="https://jztheme.com">

但说实话,现在都 2024 年了,我还真没见过谁家产品线还在认真支持 IE。所以我的建议是:直接上 preconnect,别管 dns-prefetch 了。

总结一下我现在的套路

我现在处理 preconnect 的流程是这样的:

  • 列出所有第三方域名:CDN、API、字体、埋点、广告等
  • 筛选出首屏关键路径上的,只对这些加 preconnect
  • 判断是否跨域:是 → 加 crossorigin;否 → 不加
  • 静态资源统一域名下?跳过,交给 HTTP/2 多路复用
  • 非关键路径的,在交互触发前用 JS 动态插入
  • 上线后用 Lighthouse 和 WebPageTest 验证是否生效

最后提醒一句:preconnect 最多提前个几百毫秒,别指望它拯救一个烂架构。先把图片懒加载、代码分割、接口合并这些基础优化做了,再来抠这种细节。

以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论