preload,prefetch,preconnect

一、先建立整体认知

你可以把页面加载理解成:

用户打开页面

浏览器解析 HTML

发现 CSS / JS / 图片

建立连接

下载资源

解析执行

而:

技术 提前做什么
preconnect 提前建立连接
preload 提前下载"当前页面必需资源"
prefetch 提前下载"未来页面可能资源"

你会发现:

它们优化的是不同阶段。


二、preconnect(最早阶段优化)
1、为什么它快?提前和目标服务器建立连接!

正常情况下:

复制代码
发现资源
  ↓
DNS
  ↓
TCP
  ↓
TLS
  ↓
开始下载

而 preconnect:

在资源真正请求之前:

复制代码
提前:
DNS + TCP + TLS

所以后面真正请求资源时:直接下载,省掉大量等待时间。

2、适用场景(极其常见)

尤其适合:

CDN
html 复制代码
<link rel="preconnect" href="https://cdn.xxx.com" crossorigin>

字体服务

例如:

  • Google Fonts
  • 阿里字体
  • 字节字体 CDN
html 复制代码
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>

第三方接口

例如:

  • 支付
  • 埋点
  • 图片服务
  • 视频服务
html 复制代码
<link
  rel="preconnect"
  href="https://pay.xxx.com"
  crossorigin
/>

3、注意事项
不要乱加

连接是有成本的。

浏览器:

  • socket 数有限
  • keep-alive 有成本
  • 会占用资源

一般:

复制代码
只 preconnect 最关键的域名

通常:

复制代码
1~3 个

三、preload(当前页面资源提前下载)
html 复制代码
    <!-- 关键脚本预加载 -->
    <link rel="preload" href="/iconfont.js" as="script" />
    <link rel="preload" href="/config.js" as="script" />
    <script src="/iconfont.js"></script>
    <script src="/config.js"></script>
1、核心本质
提高资源优先级

默认浏览器:并不是立刻发现所有资源。

浏览器:只有执行到 JS,才知道需要。

而 preload:在 HTML 阶段就开始下载,性能会提升很多。

2、最重要理解
  • 首屏关键但发现得晚的资源(异步路由里的字体、LCP 大图)
  • <script defer> 配合的关键脚本
  • 避免「HTML 解析到很晚才发现要下这个大文件」
3、典型场景
(1)首屏字体
html 复制代码
<link
  rel="preload"
  href="/font.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

字体特别适合 preload。

因为:

默认字体发现太晚。

会导致:

FOIT/FOUT

(2)Hero 图片

首屏大图:

html 复制代码
<link
  rel="preload"
  as="image"
  href="/banner.webp"
/>
(3)关键 JS

首屏大图:

html 复制代码
<link rel="preload" href="/main.js" as="script">
(4)关键 CSS
html 复制代码
<link rel="preload" href="/main.css" as="style">
5、as 非常重要

很多人不会。

html 复制代码
<link rel="preload" href="/main.js" as="script">

as 决定:

  • 资源优先级
  • Content Security Policy
  • 缓存复用
  • Accept 请求头

常见:

类型 as
JS script
CSS style
图片 image
字体 font
fetch fetch

6、preload 最大误区

所有资源都 preload

preload:是高优先级下载

如果滥用:会抢占带宽。导致:真正关键资源反而变慢。


7、什么时候不该 preload

例如:

  • 用户不一定会看到的图片
  • 非首屏资源
  • 路由切换后资源

这些应该:

prefetch

而不是 preload。


四、prefetch(未来资源预取)
1、核心特点
低优先级

浏览器会:

复制代码
当前页面忙完
    ↓
网络空闲
    ↓
偷偷下载

因此:

不会影响当前页面。

html 复制代码
<!-- 下一页可能展示的大图 -->
<link rel="prefetch" href="/images/banner-next.webp" as="image" />

<!-- 下一页字体 -->
<link rel="prefetch" href="/fonts/inter-latin.woff2" as="font" crossorigin />

<!-- 下一页可能请求的 JSON -->
<link rel="prefetch" href="/api/dashboard-summary.json" as="fetch" crossorigin />

2. react项目中预取下一页的 JS chunk

记录是否请求过某个JS chunk

TypeScript 复制代码
type RouteKey =
  | "alg-list"
  | "alg-flow"
  | "resource-auth"
  | "resource-container"
  | "repo-list"
  | "repo-train"
  | "repo-data-pool"
  | "repo-data-annotation";

// 写成函数,不会立即 import 引入

const routeImportMap: Record<RouteKey, () => Promise<unknown>> = {
  "alg-list": () =>
    import("@/pages/algorithmApplication/components/algListPage"),
  "alg-flow": () => import("@/pages/algorithmApplication/components/flowPage"),
  "resource-auth": () => import("@/pages/hostAuth"),
  "resource-container": () => import("@/pages/containerManage"),
  "repo-list": () => import("@/pages/repository/components/modelList"),
  "repo-train": () => import("@/pages/repository/components/modelTrain"),
  "repo-data-pool": () => import("@/pages/imgDataPool"),
  "repo-data-annotation": () => import("@/pages/dataAnnotation"),
};

const prefetched = new Set<RouteKey>();
const no_request_idle_callback_delay = 300;
/** 空闲时预取(prefetch) */
export function prefetchRoute(key: RouteKey) {
  if (prefetched.has(key)) return;
  prefetched.add(key);
  const run = () => {
    routeImportMap[key]().catch(() => {
      // 预取失败不影响主流程
      prefetched.delete(key);
    });
  };
  // 当前浏览器支持 requestIdleCallback
  // 检查 window 上是否存在 requestIdleCallback 这个属性。
  // 有则用浏览器 API,在主线程空闲时再执行 run。
  // requestIdleCallback 的作用:把预取放到"不那么忙"的时候,尽量不抢首屏渲染、交互的 CPU/网络。
  if ("requestIdleCallback" in window) {
    (
      window as Window & { requestIdleCallback: (cb: () => void) => void }
    ).requestIdleCallback(run);
  } else {
    // 不支持,走降级方案
    setTimeout(run, no_request_idle_callback_delay);
  }
}

/** 立即加载(用于 hover/focus 时提速) */
export function warmupRoute(key: RouteKey) {
  if (prefetched.has(key)) return;
  prefetched.add(key);
  routeImportMap[key]().catch(() => prefetched.delete(key));
}

export type { RouteKey };

src/router/index.tsx 做"首屏后预取次级路由"
TypeScript 复制代码
import { prefetchRoute } from "./routePrefetch";

useEffect(() => {
  // 首屏渲染后,空闲预取次级路由
  prefetchRoute("alg-flow");
  prefetchRoute("repo-list");
  prefetchRoute("resource-auth");
}, []);

在导航交互上做"hover 即 warmup"
TypeScript 复制代码
import { warmupRoute } from "@/router/routePrefetch";

// 示例:某个菜单项
<Menu.Item
  key="repo-model-list"
  onMouseEnter={() => warmupRoute("repo-list")}
  onFocus={() => warmupRoute("repo-list")}
  onClick={() => navigate("/repository/modelList")}
>
  模型仓库
</Menu.Item>
维度 只有 lazy lazy + prefetchRoute
首屏体积 更小(好) 若过早 prefetch 多个页面,会多占带宽
首次进入某页 点击后才下载 chunk,常有 loading 若已 prefetch,点击后几乎无等待
用户从不去的页 不浪费流量 可能白下载

理解:HTTP 缓存 和 ES Module 模块缓存。

第一次 import("@/pages/xxx")
  1. Vite 打包后对应一个 chunk URL,例如 /assets/FlowPage-xxxxx.js
  2. 浏览器发起网络请求,下载 JS 文件(可走 disk/memory cache)。
  3. 模块执行一次,结果放进 模块实例缓存。
第二次(无论是 lazy 触发的 import,还是 prefetchRoute 里的 import
  • 同一路径、同一 chunk:通常不会重新下载(304 或 memory cache)。
  • 模块本身:已加载过的模块,再次 import() 会返回已 resolved 的 Promise,不会重新执行模块顶层代码(除非 HMR 热更新)。
相关推荐
星恒随风3 天前
C++ string 类详解:常用接口、OJ 场景与模拟实现中的深浅拷贝
开发语言·c++·笔记·学习·状态模式
colofullove5 天前
实时游玩页与 WebSocket 状态管理实现
websocket·网络协议·状态模式
夏天测5 天前
业务逻辑漏洞实战:篡改响应包绕过登录,直入后台管理系统
渗透测试·状态模式·业务逻辑漏洞·web 安全·响应包篡改
可乐ea6 天前
【Spring Boot + MyBatis|第7篇】JWT 登录认证与拦截器实现
java·spring boot·后端·mybatis·状态模式
前端不太难6 天前
GPU 集群调度架构解析
架构·状态模式
ShiJiuD6668889996 天前
外卖项目笔记总结上 (后端板块)
状态模式
前端不太难6 天前
当 AI 接管 Workspace:鸿蒙 PC Agent 架构设计实践
人工智能·状态模式·harmonyos
Maimai108087 天前
Web3 前端实时通信如何落地:从 SSE 订阅到行情、订单与账户状态更新
前端·javascript·react.js·前端框架·web3·状态模式
不吃青椒!8 天前
LangGraph 流式事件处理:从实战到体系
ai·langchain·状态模式
前端不太难8 天前
鸿蒙游戏世界模型:实现原理 + Demo实现
游戏·状态模式·harmonyos