preload / prefetch / preconnect
资源提示(Resource Hints)是一组 HTML 特性,允许开发者主动告知浏览器关于资源加载的意图。通过 preload、prefetch、preconnect 等机制,可以显著优化页面加载性能。
前置知识
阅读本节前,建议先了解:favicon 图标
基础概念
为什么需要资源提示
浏览器默认的资源加载策略是"按需加载"——当解析到需要资源的标签时才开始加载。但这意味着关键资源可能加载较晚,导致页面渲染延迟。
资源提示让开发者能提前告知浏览器:"我接下来会需要这些资源,请尽早开始准备。"
资源提示分类
| 类型 | 机制 | 优先级 | 触发时机 |
|---|---|---|---|
preconnect | 建立连接 | 高 | 当前页面 |
dns-prefetch | DNS 解析 | 低 | 当前页面 |
preload | 预加载资源 | 高 | 当前页面 |
prefetch | 预取资源 | 最低 | 未来导航 |
modulepreload | 预加载 ES 模块 | 高 | 当前页面 |
prerender | 预渲染页面 | 低 | 未来导航(已废弃) |
语法
dns-prefetch
提前解析域名的 DNS,不建立连接:
<link rel="dns-prefetch" href="https://cdn.example.com">preconnect
提前建立 TCP 连接(含 DNS 解析、TCP 握手、TLS 协商):
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>preload
预加载当前页面即将需要的资源:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="hero-image.jpg" as="image">
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="critical.css" as="style">prefetch
预取下一页面可能需要的资源(低优先级):
<link rel="prefetch" href="next-page.js">
<link rel="prefetch" href="next-page-data.json">modulepreload
专门用于预加载 JavaScript ES 模块:
<link rel="modulepreload" href="app.mjs">详细说明
dns-prefetch 详解
工作原理:浏览器在空闲时提前解析指定域名的 DNS,将结果缓存到本地 DNS 缓存中。
适用场景:
- 页面中会从第三方域名加载资源(CDN、字体、分析工具等)
- 节省 DNS 查询时间(通常 20~120ms)
<!-- 为第三方域名预先解析 DNS -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">特点:
- 消耗极低(只做 DNS 查询)
- 适合所有第三方域名
- 可以大量使用(每个约消耗极少的资源)
preconnect 详解
工作原理:不仅解析 DNS,还完成 TCP 握手和 TLS 协商。相当于"预热"了连接。
适用场景:
- 跨域请求的关键资源(字体、API、CDN 资源等)
- 连接建立成本较高的场景(HTTPS、HTTP/2)
<!-- 为 Google Fonts 建立预连接 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>crossorigin 属性:当资源需要跨域请求时(如字体文件),必须添加 crossorigin 属性:
<!-- 有 crossorigin:建立完整的 TLS 连接 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 无 crossorigin:只做 DNS + TCP(不包含 TLS) -->
<link rel="preconnect" href="https://cdn.example.com">特点:
- 比 dns-prefetch 消耗更多资源(占用连接池)
- 适合 2~4 个关键域名
- 连接有生命周期(约 10 秒不使用会被关闭)
preload 详解
工作原理:以高优先级下载指定资源,并将其放入浏览器缓存。下载完成后资源处于"待用"状态,直到页面中实际引用到它。
as 属性(重要):告诉浏览器资源的类型,以便正确处理:
| as 值 | 资源类型 | 是否需要 crossorigin |
|---|---|---|
script | JavaScript | 同源不需要,跨域需要 |
style | CSS 样式表 | 同源不需要,跨域需要 |
font | 字体文件 | 总是需要 crossorigin |
image | 图片 | 不需要 |
fetch | fetch/XHR 请求 | 需要 |
audio | 音频 | 不需要 |
video | 视频 | 不需要 |
document | iframe 页面 | 不需要 |
embed | 嵌入内容 | 不需要 |
type 属性:进一步指定资源的 MIME 类型,帮助浏览器判断是否需要处理该资源:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="data.json" as="fetch" type="application/json">常用 preload 场景:
<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预加载首屏大图 -->
<link rel="preload" href="/images/hero-banner.webp" as="image" type="image/webp">
<!-- 预加载关键 JS -->
<link rel="preload" href="/js/critical.js" as="script">
<!-- 预加载关键 CSS -->
<link rel="preload" href="/css/critical.css" as="style">preload 不是 prefetch
preload 用于当前页面即将需要的资源(高优先级),prefetch 用于未来页面可能需要的资源(低优先级)。不要用 preload 加载下一页面的资源,那会浪费带宽。
prefetch 详解
工作原理:以最低优先级下载资源,放入 HTTP 缓存。不会影响当前页面的加载性能。
适用场景:
- 下一页面的关键资源
- 用户可能访问的页面的资源
- 搜索结果的预加载
<!-- 预取下一页面的 JS -->
<link rel="prefetch" href="/js/next-page.js">
<!-- 预取搜索结果页的资源 -->
<link rel="prefetch" href="/search-results.css">与 preload 的对比:
| 特性 | preload | prefetch |
|---|---|---|
| 优先级 | 高 | 最低 |
| 用途 | 当前页面 | 未来页面 |
| 时机 | 立即下载 | 空闲时下载 |
| 带宽影响 | 可能影响首屏 | 不影响首屏 |
| 适用资源 | 关键资源 | 非关键资源 |
modulepreload 详解
modulepreload 是专门为 ES 模块设计的预加载方式。与 preload as="script" 的区别在于:
- 自动处理模块的依赖关系
- 正确处理模块的 MIME 类型
- 与
<script type="module">配合使用
<link rel="modulepreload" href="/js/app.mjs">
<script type="module" src="/js/app.mjs"></script>prerender(已废弃)
<!-- 已废弃!不再推荐使用 -->
<!-- <link rel="prerender" href="/next-page.html"> -->prerender 会预渲染整个页面,消耗大量资源(CPU、内存、带宽)。现代浏览器已逐步移除对它的支持。
替代方案:使用 Speculation Rules API 或 Service Worker。
实战示例
完整的资源提示配置
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>资源提示配置示例</title>
<!-- ===== DNS 预解析(最低成本,可以多加) ===== -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<!-- ===== 预连接(中等成本,只用于关键域名) ===== -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- ===== 预加载关键资源(高优先级) ===== -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image" type="image/webp">
<link rel="preload" href="/css/critical.css" as="style">
<!-- ===== 预取未来页面资源(低优先级) ===== -->
<link rel="prefetch" href="/about.html">
<link rel="prefetch" href="/js/about.js">
<!-- ===== 实际资源引用 ===== -->
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap">
</head>
<body>
<h1>资源提示配置示例</h1>
</body>
</html>资源加载时序演示
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>资源加载时序</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 700px; margin: 40px auto; padding: 0 20px; }
.timeline { border-left: 3px solid #ddd; margin: 20px 0; padding-left: 20px; }
.timeline-item { position: relative; padding: 10px 0; }
.timeline-item::before { content: ''; position: absolute; left: -26px; top: 14px; width: 10px; height: 10px; border-radius: 50%; background: #0066cc; }
.timeline-item.low::before { background: #999; }
.timeline-item.high::before { background: #ff4444; }
.label { font-weight: bold; font-size: 14px; color: #666; }
.time { font-size: 12px; color: #999; }
button { padding: 10px 20px; cursor: pointer; background: #0066cc; color: white; border: none; border-radius: 4px; margin: 10px 5px; }
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap" as="style">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap">
</head>
<body>
<h1>资源加载时序</h1>
<p>使用 Performance API 观察资源加载顺序:</p>
<button id="btn-analyze">分析资源加载</button>
<div id="output" class="timeline"></div>
<script>
document.getElementById('btn-analyze').addEventListener('click', function() {
var output = document.getElementById('output');
output.innerHTML = '';
var resources = performance.getEntriesByType('resource');
resources.sort(function(a, b) { return a.startTime - b.startTime; });
resources.forEach(function(entry) {
var item = document.createElement('div');
item.className = 'timeline-item';
// 判断资源类型
var type = 'other';
if (entry.name.includes('googleapis')) type = 'preconnect';
if (entry.name.includes('gstatic')) type = 'preconnect';
var duration = Math.round(entry.duration);
var startTime = Math.round(entry.startTime);
item.innerHTML =
'<div class="label">' + entry.name.split('/').pop() + '</div>' +
'<div class="time">开始: ' + startTime + 'ms | 耗时: ' + duration + 'ms | 类型: ' + type + '</div>';
output.appendChild(item);
});
});
</script>
</body>
</html>注意事项
1. 不要过度使用 preconnect
每个 preconnect 会占用一个连接池中的连接(Chrome 限制同域名最多 6 个连接)。
2. preload 需要与实际使用匹配
<!-- 预加载字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<!-- 必须在 CSS 中使用相同的 URL -->
<style>
@font-face {
font-family: 'MainFont';
src: url('/fonts/main.woff2') format('woff2');
/* URL 必须与 preload 的 href 完全匹配 */
}
</style>3. 字体预加载必须加 crossorigin
<!-- 正确 -->
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 错误!字体跨域请求需要 crossorigin -->
<!-- <link rel="preload" href="/font.woff2" as="font"> -->4. preload 的 as 属性不能省略
<!-- 正确 -->
<link rel="preload" href="script.js" as="script">
<!-- 错误!没有 as 属性,浏览器不知道如何处理 -->
<!-- <link rel="preload" href="script.js"> -->最佳实践
1. 第三方域名使用 dns-prefetch 或 preconnect
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.example.com">2. 首屏关键字体使用 preload
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>3. 首屏大图使用 preload
<link rel="preload" href="/images/hero.webp" as="image">4. 下一页面使用 prefetch
<link rel="prefetch" href="/next-page.js">5. 避免预加载过多资源
<!-- 不推荐:预加载太多资源,反而影响首屏性能 -->
<link rel="preload" href="/js/a.js" as="script">
<link rel="preload" href="/js/b.js" as="script">
<link rel="preload" href="/js/c.js" as="script">
<!-- 使用构建工具自动生成 preload 链接 -->资源提示速查表
| 提示 | 语法 | 时机 | 优先级 | 典型场景 |
|---|---|---|---|---|
| dns-prefetch | <link rel="dns-prefetch" href="..."> | 当前页 | 最低 | 第三方域名 |
| preconnect | <link rel="preconnect" href="..." crossorigin> | 当前页 | 高 | 字体 CDN、API 服务器 |
| preload | <link rel="preload" href="..." as="..."> | 当前页 | 最高 | 关键字体、首屏大图 |
| prefetch | <link rel="prefetch" href="..."> | 未来导航 | 最低 | 下一页面的资源 |
| modulepreload | <link rel="modulepreload" href="..."> | 当前页 | 高 | ES 模块 |
下一节