# 资源提示：rel="preload" 与 rel="preconnect"

> 用 rel="preload" 提前获取特定资源与用 rel="preconnect" 预热连接之间的区别，让 preload 避免二次请求的 as/crossorigin 属性，以及根据 MDN 二者均已 Baseline 广泛可用。

**一句话：** `<link rel="preload">` 与 `<link rel="preconnect">` 都是在 `<head>` 中声明
的资源提示，但作用不同——preload 会提前获取一个具体的高优先级资源，而 preconnect 只是
预先为某个源建立连接（DNS、TCP，对 HTTPS 还包括 TLS），不获取任何内容。

## `rel="preload"`：提前获取特定资源

根据 MDN，`<link rel="preload">` 让你在页面很快就会需要某个资源时提前声明这次获取，
使浏览器在页面生命周期的早期——即浏览器主渲染流程启动之前——就开始加载它，从而降低
阻塞页面渲染的可能性。

```html
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>
```

根据 MDN，`as` 属性标识资源类型，使浏览器能够应用正确的 `Accept` 请求头、正确应用内容
安全策略，并为页面的真实请求复用预加载的资源。

## `rel="preconnect"`：预热连接，不获取任何内容

```html
<link rel="preconnect" href="https://api.example.com">
```

根据 MDN，`preconnect` 通过预先执行部分或全部连接握手，为将来来自该源的跨源请求、导航
或子资源提供先机。它对同源请求没有帮助，因为该连接本已建立。由于向过多第三方源预连接会
适得其反，MDN 建议只将其保留给最关键的少数几个源，对不那么关键的源改用 `dns-prefetch`。

## `preload` 上的 `crossorigin` 属性

根据 MDN，`crossorigin` 属性必须匹配 preload 所代表的真实请求的 CORS 与凭据模式——同源
资源也可能需要它。根据 MDN，字体无论同源与否都以匿名模式 CORS 发出请求。

## 通过脚本生成提示

```js
function preloadFont(href) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'font';
  link.type = 'font/woff2';
  link.crossOrigin = 'anonymous';
  link.href = href;
  document.head.appendChild(link);
}
```

## 运行时检测与回退

根据 MDN，`HTMLLinkElement.relList` 是反映元素 `rel` 属性的一个实时 `DOMTokenList`，
`DOMTokenList.supports()` 会报告某个 token 是否受支持——用它在依赖任一提示前先做特性检测：

```js
function supportsResourceHint(rel) {
  const link = document.createElement('link');
  // 不仅要判断 relList 是否存在，还要判断 supports 本身是否是函数：
  // 调用一个不存在的方法会抛出异常，而不是返回 false，这会让检测失效。
  return 'relList' in link && typeof link.relList.supports === 'function' && link.relList.supports(rel);
}

if (supportsResourceHint('preload')) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'font';
  link.type = 'font/woff2';
  link.crossOrigin = 'anonymous';
  link.href = '/fonts/brand.woff2';
  document.head.appendChild(link);
} else {
  // preload 不受支持（或没有 relList.supports）：无需额外回退——
  // 浏览器仍会在真正的 @font-face 请求触发时发现并获取该字体，
  // 只是少了这个提示原本能带来的提前起步。
}

if (supportsResourceHint('preconnect')) {
  const link = document.createElement('link');
  link.rel = 'preconnect';
  link.href = 'https://api.example.com';
  document.head.appendChild(link);
} else {
  // preconnect 不受支持：无需回退，浏览器在发出真实的跨源请求时
  // 仍会正常建立连接。
}
```

## 支持情况

根据 MDN，`rel="preload"` 与 `rel="preconnect"` 均已 Baseline **广泛可用**——preload 自
2021 年 1 月起，preconnect 自 2020 年 1 月起——意味着在 Baseline 浏览器中已保持两年多的
一致支持。

## 实践清单

- [ ] 为每个 `preload` 链接设置 `as`，使其与资源的真实请求类型匹配（`font`、`style`、
      `script`、`image` 等）。
- [ ] 只要真实请求会以 CORS 模式发出就添加 `crossorigin`——字体请求始终需要，即使同源
      也不例外——以便与真实请求匹配。
- [ ] 根据 MDN，将 `preconnect` 保留给最关键的连接；其余第三方源改用 `dns-prefetch`。
- [ ] 只 preload 当前页面真正尽早需要的资源——根据 MDN，预加载未被使用的替代格式可能
      浪费带宽。

## 相关参考

- [导航预加载](/zh/reference/performance/navigation-preload/)
- [Speculation Rules](/zh/reference/performance/speculation-rules/)