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 热更新)。
相关推荐
前端不太难2 小时前
内存带宽、超长上下文与解码效率:AI推理的三大核心制约
人工智能·状态模式
五阿哥永琪12 小时前
前后端跨域的解决办法
状态模式
Komorebi_99991 天前
LangChain Day6 前端视角:简易接口联调思路
状态模式
叶小鸡1 天前
Java 篇-项目实战-AI 天机学堂(从 0 到 1)-day4
状态模式
shjsjdmmd1 天前
Claude API 流式输出(SSE)实战指南:从打字机效果到 Agent 工具调用完整落地
状态模式
前端不太难1 天前
解码大模型的效率革命:当算力不再是唯一瓶颈
状态模式
前端不太难1 天前
从算力到存力:AI性能的决定性因素正在重构
人工智能·重构·状态模式
Python私教2 天前
从主题闪烁到 Markdown 阅读体验:RuyiBlog v0.1.1 的前端实现复盘
前端·状态模式
ForgeAI码匠2 天前
后台权限不只是菜单隐藏:Forge Admin 的 RBAC 权限链路拆解
java·spring boot·spring·状态模式