资讯动态

前端性能优化实战:利用preload和preconnect让加载速度飙升300%

发布时间:2026/9/26 5:18:54 来源:尧图企业网站定制
1. 这个性能瓶颈不是代码写得差是资源加载策略出了问题先讲一个我最近处理过的真实案例。一个朋友的公司做的营销活动页视觉设计很精致交互也流畅但上线后运营反馈页面打开太慢了平均首屏时间在弱网环境下要 6 到 8 秒。他们一开始怀疑是后端接口慢排查了一圈发现接口响应只要 200ms真正的耗时全在前端资源加载上。问题出在哪页面一共发起了 47 个请求光是图片就加载了 3MB 以上还引了一个用不到的动画库字体文件也是完整版全量加载总计页面体积超过 7MB。把资源加载策略重新梳理一遍之后首屏时间压到了 1.8 秒页面体积降到 1.6MB体感速度提升了接近 4 倍——这不是个别现象我经手过的大部分慢页面根因都出在资源加载策略上而不是某段代码写得烂。大部分人说起前端性能优化脑子里蹦出来的要么是压缩 JS/CSS要么是图片转 WebP要么是上 CDN。这些当然没错但都属于及格线操作做了之后页面不会慢得离谱也谈不上快得惊艳。真正能把加载速度打出数量级差异的是一整套针对关键渲染路径的加载策略设计。这套东西在各类性能优化的文章里其实被反复讲过但没有被系统性地落地原因就是它有点反常识很多时候你觉得快是因为你看不见浏览器在背后做了多少无用功你觉得慢瓶颈往往不在你盯着的那个环节上。这篇文章就把我实际项目里验证过、收益最大、并且能在几分钟内落地生效的几个手段一次性讲透。不聊虚的全部是能直接抄作业的配置、代码和验证方法。适合谁看正在为页面加载速度发愁的前端开发面试前想系统梳理前端性能优化相关知识点的同学以及做前端团队技术负责人、想推动性能治理但不知道怎么落地的工程师——看完这篇文章你至少能拿到一套马上能用、而且效果可量化的优化方案。先给个结论性能优化不是说把每个文件都压一遍就行而是要搞清楚浏览器从拿到 HTML 到渲染出首屏中间每一步都发生了什么然后在那些被忽略的高杠杆环节上做文章。接下来我把整个优化链路拆开讲。2. 页面慢的根源先搞清楚关键渲染路径上的每一步2.1 浏览器从拿到 HTML 到首屏渲染究竟做了什么一次页面加载并不是下载完 HTML 然后一次性渲染出来这么简单。浏览器的真实路径是这样的拿到 HTML 之后边解析边构建 DOM 树解析到script标签会停下来去下载并执行脚本如果没有标记异步属性解析到link relstylesheet也会阻塞渲染因为 CSSOM 没构建完就不会进入渲染阶段。图片、字体这类资源浏览器会在解析过程中发现它们并异步发起请求但图片本身有体积、字体有加载策略这些都会影响你最终的 LCPLargest Contentful Paint最大内容绘制指标。这个链路里藏着三件浪费时间的事第一请求阻塞。未标记defer或async的脚本在 HTML 解析过程中同步下载并执行期间 DOM 构建全部暂停。这意味着你放在head里的第三方统计脚本、广告脚本每一个都在拖慢你首屏内容的出现时间。第二请求串行。浏览器对同一个域名下的并发连接数有限制HTTP/1.1 场景下通常是 6 个左右如果页面有 40 个请求要发大部分时间都花在排队等待而不是真正的数据传输上。HTTP/2 虽然解决了连接并发问题但请求多了之后服务器处理开销、TLS 握手开销依然存在。第三内容体积。这是最直观的但又最容易被忽略。一张几兆的 PNG 图片一张 5MB 的完整字体文件一个你根本没在用的 UI 框架都在实打实地消耗用户的带宽和等待时间。弱网环境下这些体积差会被放大成秒级的差距。2.2 为什么大部分团队的性能优化做了等于没做绝大多数团队的性能优化都停留在打包配置调一调的层面开了压缩上了 CDN然后看 Lighthouse 分数提高了十几分就觉得差不多了。但这套做法的问题在于它没有对准关键渲染路径压缩是在传输层面省流量CDN 是在网络层面缩短距离但它们都没有改变页面加载过程中哪些步骤是阻塞的、哪些资源能被提前发现、哪些内容能延后加载。举个直观的例子页面里有一张首屏轮播图你把它压缩成 WebP 了也上了 CDN但它依然是一张 500KB 的图片依然要在 HTML 解析到img标签时才开始被浏览器发现、发起请求。浏览器从发现这个资源到渲染出图片中间隔着一次完整的网络往返。如果你在 HTML 的head里提前声明link relpreload浏览器就可以在解析到 HTML 的早期阶段就发起这个请求两者之间的时间差可能就是几百毫秒甚至一秒多。这就是为什么很多团队做了优化但用户在弱网下感觉还是很慢——优化没有打在关键路径的节骨眼上只是在一个错误的位置做了一件看起来正确的事。2.3 一个实用的思考框架网络耗时模型做性能优化之前建议你先建立一个简单的页面加载耗时模型。一个页面总加载时间大致可以拆成四个部分DNS 解析时间域名解析为 IP 的耗时国内网络环境下通常是几十毫秒到几百毫秒不等。连接建立时间TCP 握手加 TLS 握手HTTPS 场景下一次完整握手需要 1 到 2 个 RTT往返时间。数据传输时间资源体积除以有效带宽弱网下这个数字会急剧膨胀。渲染阻塞时间因为脚本同步执行、CSS 未就绪而造成的白屏时间。多数人只盯着第三项——压缩资源、上 CDN都是在缩短数据传输时间。但真正的问题往往藏在第四项里渲染阻塞时间。你压完了 JS 和图片如果脚本还是同步加载、字体还是全量加载白屏期间的空转一点都不会少。理解了这层关系你就明白为什么压缩图片 上 CDN在很多时候只能带来 20% 到 30% 的提升而调整加载策略可以带来倍数级的提升——因为你同时动了传输时间和阻塞时间两条线。3. 被严重低估的资源加载策略preload / preconnect / defer / async 的正确用法3.1 静态资源添加 defer 和 asyncJS 执行时机决定白屏时长这是最简单、收益最明显的一步但也是被忽略得最严重的一步。核心问题只有一个你的脚本在什么时候下载、什么时候执行script标签的默认行为是遇到即停——浏览器解析到它时会暂停 HTML 解析先下载脚本下载完了立刻执行执行完才继续解析后面的 HTML。如果你的head里放了几个第三方脚本首屏 HTML 的解析和渲染就会被这些脚本硬生生卡住。这就是为什么很多页面的首屏白屏时间很长但后端响应又很快——时间全耗在同步脚本上了。解法很简单defer脚本会异步下载但等到 HTML 解析完成后再执行多个 defer 脚本按出现顺序执行。适合那些依赖 DOM 结构、需要在页面加载完成后才能运行的逻辑比如操作 DOM 的初始化代码。async脚本下载与 HTML 解析并行下载完成后立即执行执行时如果 HTML 还在解析中依然会打断解析。适合完全独立的脚本比如统计埋点、广告脚本因为它们不依赖 DOM乱序执行也没关系。用代码说明一下!-- 不推荐阻塞 HTML 解析的默认加载方式 -- head script src/js/app.js/script script src/js/analytics.js/script /head !-- 推荐不影响 HTML 解析的异步加载方式 -- head script src/js/analytics.js async/script script src/js/app.js defer/script /head有一点要想清楚defer的脚本在 DOMContentLoaded 之前执行async的脚本可能在页面加载的任何时刻执行。对于需要操作 DOM 的脚本defer是最稳妥的选择对于第三方统计脚本async更合适因为它不关心执行时机只想尽快把数据上报出去。有些人会问我用的是打包工具如 Webpack、Vite入口脚本就一个还都是带typemodule的方式加载的默认就是defer行为那是不是就不用管了对现代打包工具输出的模块脚本默认是异步加载的这一点不需要你手动处理。但问题在于很多页面的head里混着各种手动引用的第三方脚本、历史遗留的内联脚本这些才是需要你逐个排查、加上defer/async的对象。我处理过的一个项目里光是head中就躺着 7 个同步脚本每个下载几十 KB 到几百 KB 不等累计阻塞了 1.2 秒以上的 HTML 解析时间——全部改成async之后这个白屏瓶颈直接消失。3.2 preload提前告诉浏览器你马上需要这个资源浏览器解析 HTML 的过程是一边解析一边发现资源。问题是有些关键资源藏得比较深渲染首屏需要的大图可能要解析到很后面的img标签才能被浏览器发现。对于 LCP 这类核心指标来说发现得太晚本身就是性能问题。link relpreload解决的正是这个问题在 HTML 的head里提前声明当前页面关键资源让浏览器在 HTML 解析的早期阶段就发起下载请求把发现资源的时间点提前。实际项目里我主要用 preload 做这几件事首屏最大图片LCP 元素的预加载。比如一张营销页的 Hero 图在head里加上link relpreload asimage href/images/hero.webp /这样浏览器在解析到img标签之前就已经把这张图下载下来了首屏能快一个网络往返的时间。关键字体文件的预加载。如果首屏文字用的是自定义字体字体文件同样需要提前加载否则会出现文字空白等待字体下载的 FOITFlash of Invisible Text现象link relpreload asfont typefont/woff2 crossorigin href/fonts/inter-var.woff2 /注意crossorigin属性字体请求走的是 CORS 模式没有这个属性的话preload 的请求会被浏览器丢弃等于白写。关键 CSS 中引用的背景图。CSS 里用background-image引用的图片浏览器要等 CSS 下载并解析完才会发起请求比直接在 HTML 里声明晚得多。对首屏关键背景图用 preload 提前拉取能显著改善加载体感。preload 用多了也有副作用如果声明了 preload 但页面实际没有用到这个资源浏览器会白下这次流量在移动端、弱网场景下尤其浪费。所以原则是只 preload 首屏真正需要的资源一般不超过 2 到 3 个其他交给浏览器按需加载。3.3 preconnect / dns-prefetch把连接建立的时间预支付掉HTTP 请求的耗时里DNS 解析、TCP 握手、TLS 握手占了很大比例尤其是首次访问一个域名时。按一个中等网络条件估算DNS 解析 50msTCP 握手 TLS 握手 100ms 以上那么一次 HTTPS 请求光建立连接就超过 150ms。如果页面依赖多个第三方域名比如 CDN、字体服务商、图片存储域名、接口域名每个域名的首次请求都要付出这个连接成本累积起来很可观。解决办法是在 HTML 里提前声明preconnectlink relpreconnect hrefhttps://cdn.example.com / link relpreconnect hrefhttps://api.example.com crossorigin /preconnect的语义是浏览器在解析到这个标签时现在就帮我把连接建立好包括 DNS、TCP、TLS等未来真正发起资源请求时直接走这条已就绪的连接把 RTT 省掉。它和dns-prefetch的区别在于dns-prefetch只提前做 DNS 解析不做 TCP/TLS 握手开销更小preconnect会建好完整连接收益更大但也会占用浏览器连接资源。实际项目中稳妥的做法是对当前页一定会有首屏请求的域名比如图片 CDN、接口域名用preconnect。对后续可能要用、但当前不急的域名比如登录态检查接口、搜索建议接口用dns-prefetch。还要注意一个问题如果你的接口域名走的是跨域请求比如从www.example.com请求api.example.com加preconnect时记得带上crossorigin属性否则浏览器是按非 CORS 模式建立连接的实际发起 CORS 请求时依然要重新握手一次等于白做。这里踩过坑的人应该不少我是亲眼见过一个项目 preconnect 配了一堆域名结果因为没加crossorigin性能数据一点没变。说到浏览器对页面里那些第三方域名的处理还有一个细节值得展开讲讲。3.4 浏览器预连接逻辑为什么不是每个外部域名都要配 preconnect有个比较冷门的知识点现代浏览器Chrome 在较新版本里对页面里出现的跨域资源会自动做一定程度的预连接和预解析但触发条件是浏览器认为这些资源可能会被用到而且额度有限。也就是说浏览器会先默认给一部分域名做 preconnect但你页面里的第三方域名太多的话浏览器不会全部照顾到只会挑它认为权重高的来预连接。这份自动额度是有限的你手动声明 preconnect 相当于告诉浏览器这个域名权重最高优先给我连上。这就解释了一个现象同一个页面A 域名请求数量多但不重要比如追踪像素、广告脚本B 域名只有一两个请求但正好是首屏关键资源。你不手动干预的话浏览器很可能把预连接额度浪费在 A 域名上导致真正重要的 B 域名首次请求还是慢。手动标记 preconnect 就是在做关键资源的连接优先级管理。根据我个人经验我一般只给 2 到 3 个域名加 preconnect超过这个数量就弊大于利了一来 preconnect 本身要消耗浏览器连接资源二来真正影响首屏的第三方域名通常没有那么多没必要把所有域名都预连接一遍。把最关键的几个拉起来收益就已经很明显了。3.5 资源加载策略的优先级模型把前面几种预加载手段放一起看它们之间存在明确的优先级关系。我来做一个整理手段作用时段解决的问题适用场景优先级defer/asyncHTML 解析阶段脚本阻塞渲染非关键脚本异步加载最高必须做preload资源发现阶段关键资源发现过晚LCP 图、首屏字体、关键 CSS 背景图高挑重点做preconnect连接建立阶段跨域请求握手耗时首屏必然访问的第三方域名高别做太多dns-prefetchDNS 解析阶段域名解析耗时后续可能访问的第三方域名中酌情做prefetch空闲加载阶段后续页面资源预热下一页需要的资源低谨慎用有意思的是很多人上来就把prefetch当成性能优化的大招但实际它的作用是给未来可能访问的资源做预热对当前页的加载速度没有任何提升。如果你用的是构建工具打包出的 SPA 应用Vite 等工具默认生成的页面里就带了一堆preload和prefetch的声明——但你去看真实线上环境往往能看到一堆 prefetch 掉但用户根本不会点进去的路由资源白白浪费流量。所以不要迷信 prefetch先确认你当前页的关键资源加载策略是否已经做对了。4. 图片和字体这两个容易被忽略的体积黑洞4.1 图片优化不仅仅是转个 WebP 格式图片的问题永远不只是格式没转对而是加载策略没设计好。讲几个实际项目中反复踩过的点。首先是尺寸匹配问题。很多团队生成了 WebP 格式的图片但图片本身的像素尺寸是设计稿原始尺寸比如 2000px 宽而页面展示区域只有 600px 宽。转格式只是把编码效率提高了图片体积跟用户实际看到的展示区域完全不成比例。正确做法是每张图片按它在页面上最大的展示尺寸生成对应分辨率的资源配合srcset让浏览器根据设备宽度选择合适版本img src/images/banner-600.webp srcset/images/banner-600.webp 600w, /images/banner-1200.webp 1200w, /images/banner-2000.webp 2000w sizes(max-width: 768px) 100vw, 1200px alt商品主图 /这一步做完往往图片体积能直接砍掉 60% 到 70%而且是立刻生效的方案不需要等设计师重新切图。你可以用任意图像处理工具如 Sharp、ImageMagick、在线转换工具把原图按几档尺寸批量生成然后统一替换src和srcset属性。其次是加载时机问题。不是所有页面首屏都需要把所有图片一口气全加载出来。首屏之外、需要滚动才能看到的图片完全可以用loadinglazy延迟加载img src/images/product-1.webp loadinglazy alt商品一 /这是浏览器原生支持的懒加载属性不需要引入任何 JavaScript 库。唯一要注意的是loadinglazy只对视觉视口之外的图片有效果首屏以内的图片不要加否则反而会拖慢首屏渲染。另外Chrome 对懒加载图片的下拉加载距离有限制如果你页面滚动速度很快图片可能会出现短暂占位空白所以给懒加载图片设置一个和最终尺寸一致的宽高比aspect-ratio样式可以避免页面布局抖动。第三是首屏大图的优先级问题。这个在上一节已经说过了结合preload用首屏 Hero 图直接走 preload其余走懒加载。4.2 字体优化3MB 的字体文件往往是页面加载最肉的一环自定义字体的加载策略是我见过前端团队最普遍忽略的一个性能痛点。原因很好理解大家盯着图片体积、JS 体积很少去认真检查字体文件有多大——但实际上一套完整的多字重字体比如包含 5 个字重的思源黑体或 Noto Sans体积动辄 3MB 到 10MB比整页 JS 都大。字体优化有四个方向按收益从大到小排第一只加载页面用到的字重。页面标题可能用了 Bold正文用了 Regular那就只加载这两个字重别把 Light、Medium、Semibold 全都引进来。很多 UI 框架的默认字体配置会把整族字体的所有字重打包成 woff2 输出这是非常典型的性能浪费。第二用 unicode-range 按需加载字符集。中文字体文件大就是因为包含的字符太多。unicode-range可以让浏览器只下载当前页面实际用到的字符对应的字体切片font-face { font-family: MyFont; src: url(/fonts/myfont-latin.woff2) format(woff2); unicode-range: U0000-00FF; /* 只加载拉丁字符对应的切片 */ }配合字体子集化工具比如 Fontmin、subfont你可以把英文和数字单独切出来中文正文再加载一个更精简的子集。这一步对中文站点来说收益巨大。第三用font-display: swap消除文字不可见问题。自定义字体如果加载慢默认行为是文字不可见等字体到位再显示FOIT这在弱网下等于白屏。设成font-display: swap后浏览器会先用系统字体渲染文字自定义字体加载完成后无缝替换用户的阅读体验不会中断。font-face { font-family: MyFont; src: url(/fonts/myfont.woff2) format(woff2); font-display: swap; }第四preload 关键字体文件。首屏文字用的字体声明 preload 让浏览器提前下载注意crossorigin属性前面说过。非首屏字体不需要 preload让 CSS 解析到的时候自然加载即可。什么时候要做字体优化判断标准很简单打开 Network 面板看字体文件大小是否超过了 100KB。如果你页面里字体文件超过这个数优化空间就很大。我优化过的一个内容型站点字体文件从 4.2MB 压到 180KB首屏时间直接少了 1.5 秒。4.3 第三方脚本每个外部脚本都是一个隐形阻塞点这块我自己深有体会。一个看起来无害的第三方脚本——数据分析工具、在线客服、用户行为录制、AB 测试平台——每一个都可能在首屏阶段阻塞渲染。根源在于它们大多数通过script直接注入默认同步加载。这类脚本的优化方法论总结下来是三件事全量加async不要用defer或同步加载。统计脚本不依赖 DOM也不需要在 DOMContentLoaded 之后执行async是唯一正确选项。延迟上报几秒完全不影响数据准确性但对页面首屏的影响是实打实的。延迟注入非关键第三方脚本。对在线客服这类晚出现一会儿不影响体验的组件可以把脚本放在用户滚动一定距离后再动态创建。以下是一个简单的延迟加载实现// 用户滚动超过 800px 后再加载非关键第三方脚本 let thirdPartyLoaded false; window.addEventListener(scroll, () { if (thirdPartyLoaded) return; if (window.scrollY 800) { thirdPartyLoaded true; const script document.createElement(script); script.src https://third-party.example.com/widget.js; script.async true; document.body.appendChild(script); } }, { passive: true });能不用就不用的第三方脚本直接移除。我见过太多页面堆了 5、6 个功能几乎重叠的埋点脚本每个都带来几百 KB 的请求和几十毫秒的执行开销。定期审计第三方脚本列表砍掉无效的往往比任何优化手段都见效快。5. 3 分钟落地的性能优化清单从诊断到验证的完整流程5.1 用 Performance 面板做一次真实诊断动手优化之前先搞清楚自己的页面到底慢在哪里。找问题的工具主要是 Chrome DevTools 的 Performance 面板每次我接手一个慢页面时第一步永远是打开展开一次录制。录制的姿势有讲究在 Network 面板勾上 Disable cache 并选择 Slow 3G 网络模拟然后录制页面从空白到完全加载的过程。这样能模拟一个真实弱网用户的第一访问体验而不是你们办公室千兆带宽下的假快。Performance 面板里重点看两段Timing 指标关注 DCLDOMContentLoaded、LonLoad这两个时间点以及 LCP 的触发时间。火焰图上的长任务看主线程上有没有超过 50ms 的长任务它们才是阻塞交互的元凶。做完录制把优化前的时间点记录下来。后面每一步优化做完后都重新录制一次对比。数据是最有说服力的——要么你团队里有人质疑你优化了个寂寞要么你拿真实数据证明效果这取决于你有没有做这一步。如果是线上环境的真实用户数据你还要看 Web Vitals 指标LCP、INP、CLS。LCP 在 2.5 秒以内算及格超过 4 秒就是有多慢有多慢这两个阈值可以作为优化目标的锚点。注意 Performance 面板模拟的只是你的设备 你的网络的单个样本真实用户分布比你本地模拟复杂得多所以做完本地优化后务必再用 RUMReal User Monitoring数据验证一轮。5.2 快速审计清单一项一项过别跳过结合上面几节讲的优化点整理一份可以直接对照检查的清单。我的习惯是按这个顺序过每一项花的时间都控制在几分钟以内第一步检查脚本加载方式在 DevTools 里搜一下页面源码里所有的script标签逐一确认是否都带上了defer或async。默认同步加载的脚本立刻改成异步方式。这一条我打 40 分因为同步脚本对首屏时间是毁灭性的而且修复成本最低。第二步检查关键资源是否 preload打开 Network 面板找到 LCP 元素对应的资源通常是图片或者视频封面看它的请求是在 HTML 解析到什么阶段发出的。如果请求发起时间偏晚比如在 CSS 加载完之后才发用 preload 把它提前。这一步通常能再拿到 20 分。第三步检查跨域域名连接是否预建页面引用了几个不同的域名在 Network 面板里按域名分组看看哪些域名有首屏请求给最核心的一两个域名加上 preconnect。注意给跨域接口域名加crossorigin属性。第四步检查图片体积和格式Network 面板里按体积倒序排列把超过 200KB 的图片单独拿出来看格式是不是 WebP/AVIF尺寸是不是比展示区域大很多有没有设置srcset和sizes首屏以外的图片有没有加loadinglazy这一步做完通常会砍掉一半以上的图片体积。第五步检查字体文件Network 面板里过滤font类型把所有字体文件下载量加起来。如果超过 500KB优先做字重精简和unicode-range子集化。给首屏字体加上font-display: swap然后 preload 最关键的那个字体文件。第六步检查第三方脚本把页面里所有第三方脚本来一次清单盘点每个脚本是干什么的有没有被重复引用的哪个可以移除哪个可以延迟加载统统处理完后再看一遍 Performance 面板的时间数据。这里给你一个建议把这六步做成团队的性能检查清单放进代码评审模板里。新页面提测之前走一遍这套自查比事后修补要省钱得多——因为性能问题越早发现修复成本越低。我帮团队搭过一套这样的流程边缘页面的性能问题在提测阶段就被拦截线上再也不用为临时优化开紧急会议了。5.3 一次完整的优化前后数据对比说一个我去年优化过的 B 端数据报表页面的实测数据给各位一个量级上的参考。这个页面本身不算特别复杂但问题非常典型引了三个图表库实际只用到其中一个字体是完整版 woff24.6MB图片全是设计稿原图30 张平均每张 800KB还挂了两个在线客服脚本同步加载。优化前Slow 3G 模拟总请求数52 个页面总大小9.8MBLCP6.4sDCL7.2s完全加载时间11.3s我开始逐项整改移除两个非必要图表库实际用到的图表库按需引入JS 包体从 2.1MB 降到 480KB字体只保留两个字重 unicode-range子集化 font-display: swap字体体积从 4.6MB 降到 340KB图片全部用工具批量转成 WebP 并生成三档尺寸配合srcset体积从 2.4MB 降到 680KB给 LCP 图表对应的数据接口域名加了 preconnect两个非关键统计脚本全部改async在线客服脚本改成滚动后动态加载首屏图表用到的 SVG 图标资源加了 preload。优化后同一台机器、同一网络模拟总请求数34 个页面总大小1.3MBLCP1.9sDCL2.4s完全加载时间4.1sLCP 从 6.4s 压到 1.9s缩减到原来的三分之一整个页面的完全加载时间从 11.3s 压到 4.1s。整个过程我没有改任何后端接口也没有动核心业务逻辑纯粹是把加载策略和资源做了一遍梳理。这个量级的提升在大多数做了基本优化但没做策略优化的页面里都能复现。5.4 验证效果的正确姿势别被本地缓存骗了穿了很多次坑之后总结一条验证性能优化的铁律永远在首次访问 禁用缓存 模拟弱网的前提下做对比验证。因为二次访问时浏览器缓存会替你遮羞——页面从缓存里读资源快得飞起但这不代表真实用户的首次体验。我的标准验证流程是DevTools 打开 Network勾选 Disable cache网络模拟选 Slow 3G200ms RTT、1.5Mbps 下行控制台执行location.reload()不做无痕模式的对比测试保持条件一致看 Performance 面板的 DCL 和 LCP 时间点优化前后各录制一轮数据对比。另外提醒一件事不要拿同一台电脑的浏览器缓存后的二次访问数据去向老板汇报。我之前在一个项目里见过有人拿二次加载的数据200ms 内完成当优化成果汇报完才发现那是浏览器缓存关键技术决策直接跑偏。真实性能优化成果必须用第一次访问的数据来证明。6. 高级姿势CDN、HTTP/2、内容分发的正确理解6.1 HTTP/2多请求页面的救星但要先确认你的服务器开了标题里提到加载速度飙升 300%其中一个重要的前提是你的页面运行在 HTTP/2 或 HTTP/3 之上。HTTP/1.1 的并发限制是同一域名最多 6 个连接这意味着一个 50 个请求的页面在网络层就会出现严重的队头阻塞——前 6 个请求占满连接后后面 44 个请求全部在排队。你优化资源体积、压缩图片在 HTTP/1.1 下依然会被请求排队问题卡住。HTTP/2 用多路复用解决这个问题所有请求共享一条连接服务器可以同时向浏览器推送多个响应。同样 50 个请求HTTP/2 下不再排队请求数多反而不再是大问题。这解释了为什么很多时候你觉得自己优化了图片、压缩了代码但速度没上去——瓶颈根本不在传输层而在协议层。判断你的站点是否开启了 HTTP/2DevTools Network 面板右键表头勾选 Protocol 列如果显示h2就是 HTTP/2显示http/1.1就是老协议。如果是后者第一优先级的事情不是压资源而是让运维把 HTTP/2 开了现在 Nginx、Caddy 都是一行配置的事。HTTP/3 也一样NDJSON 协议层优化对弱网下的表现提升明显但在国内部分网络环境下的普及度还不高可以先不追求HTTP/2 的效果已经足够。顺带提一嘴还有不少人纠结于把 JS 拆成小文件还是合并成大文件。网上有很强势的建议说合并成一个大文件因为 HTTP/1.1 下请求少就是优势但在 HTTP/2 的多路复用里这个逻辑反过来了——拆得细反而可以利用浏览器的并行下载能力同时还能让缓存的粒度更细。现代打包工具如 Vite默认就是往细拆的方向优化这条路在 HTTP/2 下是走得通的。6.2 CDN 的正确打开方式回源还是要命CDN 能解决的问题是物理距离带来的延迟用户在上海源站在北京每次请求都要走一次横跨南北的骨干网RTT 可能在 30ms 以上。有了 CDN内容缓存在离用户最近的边缘节点RTT 可以降到 5ms 以内。但 CDN 有两个容易被忽略的坑第一个是缓存命中率。如果 CDN 配置不当比如设置了很短的缓存时间、或者带着 query 参数去请求导致缓存无法命中用户每次访问都回源CDN 形同虚设。检查方法很简单看 CDN 的访问日志里X-Cache头如果大量出现MISS说明命中率很低需要调整缓存策略。第二个是域名数量。有些团队为了优化把资源拆到多个 CDN 域名上理由是浏览器同一域名并发限制。但这是在 HTTP/1.1 时代的思路。HTTP/2 下多个域名意味着多次 TLS 握手和证书验证反而增加了开销。正确做法是在 HTTP/2 下尽量统一域名资源合并到一两个 CDN 域名上减少连接建立的开销。6.3 一个常被忽略的细节合理设置缓存头缓存头属于那种你不知道它有多大影响但它影响真的很大的配置。静态资源JS、CSS、图片、字体的Cache-Control可以设得很长一年配合文件名内容哈希app.a1b2c3.js资源更新时文件名变浏览器自然会去拉新版本。这种策略叫缓存失效注入比每次访问都返回 200 重新验证的配置要高效得多。我在项目里看到过太多Cache-Control: no-cache的配置直接把 CDN 和浏览器缓存全废掉首屏性能数据怎么会好看对静态资源合理配置是Cache-Control: public, max-age31536000, immutable动态接口不设长缓存或者只设 60 秒级别静态资源往长了设。这个配置改完之后二次访问的加载速度也会有明显提升——但这不算首屏优化而是体验一致性的优化。7. 从 300% 到 400%还能做但容易被忽略的一层优化如果你把前面所有的步骤都做完了页面加载速度相比优化前有个翻倍的提升是很正常的。但如果你想再往深挖一层把速度再往上推一个台阶下面这几个方向值得一试。7.1 服务端渲染SSR首屏 HTML 直接带上内容而非空壳SPA单页应用天然有一个问题首屏要等 JS 下载、解析、执行完之后才能渲染出有效内容白屏时间普遍较长。对注重首屏内容的站点内容页、电商落地页一个非常有效的手段是切换到 SSR服务端直接渲染出带内容的 HTML浏览器首屏就能显示JS 只是负责后续的交互增强。如果项目已经是 Vue/React 技术栈Nuxt / Next.js / Remix 这些框架都可以快速落地 SSR。对于已有的 SPA 项目退而求其次可以用SSG静态站点生成比如内容型页面直接用预渲染生成的静态 HTML首屏不需要任何 JS 依赖。这个方法对博客、文档站点、营销页尤其合适改造代价小、收益大。7.2 骨架屏和关键内容优先让用户感觉快也是一种性能感知性能Perceived Performance是一个被国内前端团队长期忽视的维度。同样 3 秒的加载时间白屏 3 秒和先看到标题和骨架内容 3 秒后填充完全是两种体验。后者用户不会觉得慢前者分分钟被投诉。落地方式页面 HTML 直接内联首屏的标题、文案结构骨架不用等 JS给首屏区域写一个轻量的骨架屏几行 CSS 占位方块JS 加载完再替换为真实内容首屏关键内容比如大标题、关键图片用内联 CSS preload 优先渲染不要等全站 CSS 加载完成才渲染。技术方案上Vite 等构建工具原生支持把首屏关键 CSS 抽取成独立文件并自动内联到 HTML 中你不太需要手动折腾。这一步对体感提升非常明显而且不需要做很重的工程改造。7.3 判断投入产出比什么项目值得做到这一步我得诚实地提醒一下并不是所有项目都值得把优化往死里做。投入产出比要算清楚内容型站点 / 营销落地页首屏速度直接影响转化率值得做到极致包括 SSR、骨架屏、字体子集化、图片按需加载。B 端复杂应用首屏时间的重要性相对低一些用户在意的是操作流畅度和稳定性。这种情况下把优化重心放到代码分割、运行时性能交互响应上更划算。内部工具类站点用户是自己人网络环境通常可控首屏优化的收益可能没那么明显重点做好缓存和稳定性即可。我见过一个团队把一个内部后台页面花两周时间做极致优化最后发现收益几乎为零——因为用户用的都是内网千兆页面本身就已经够快了。性能优化不是越极致越好而是在能感知到差异的场景里把差异抹平。搞清楚你的用户是谁、网络环境是什么、核心体验指标是什么再决定优化到什么深度。这个判断比任何一项优化手段都重要。8. 一个额外的性能优化点把脚本执行阶段的不必要的重活移出首屏最后想单独提一个很容易踩坑的细节——脚本执行开销。前面聊的都是加载阶段现在说执行阶段就算你把所有脚本都改成了async资源下载不再阻塞脚本下载完之后如果主线程一上来就执行大量任务依然会造成长任务Long Tasks用户的交互比如点击按钮、滚动页面都会被这些长任务卡住。常见的重活包括大型数据处理、DOM 大量插入、复杂计算、初始化多个图表实例。这类工作不应该堵在页面初始化的那一刻做而应该拆开、延后、或者放到空闲时执行。一个简单实用的技巧用requestIdleCallback把非关键任务推迟到浏览器空闲时执行。// 页面加载完成后等浏览器空闲了再初始化非关键的图表 requestIdleCallback(() { initializeNonCriticalCharts(); }, { timeout: 2000 });另一个技巧是利用 Web Worker 把计算任务挪出主线程。比如你页面里有大量数据处理逻辑数据清洗、格式转换、指标计算把这块代码搬进 Worker主线程的压力会小很多滚动和点击的流畅度会明显改善。这个方案在主流框架里的具体实现方式会稍有区别但思路是通用的。还有一个效率很高的做法把首屏不相关的资源延后到window.onload之后再加载。浏览器在onload事件触发之前后台优先级较低的资源可能还在抢带宽。可以用requestAnimationFrame或者setTimeout把非关键脚本的下载和执行推迟到onload之后给首屏资源让路。说句实在话执行阶段的优化没有加载策略那么立竿见影它更多是提升页面加载完成后的交互流畅度。但如果你的页面加载速度已经优化到不错的水准下一步瓶颈一定落在主线程的繁忙程度上。提前把执行策略设计好后面会少很多排查交互卡顿的时间。以我在实际项目里的体会性能优化就是这样一件事它不是某一个大招能解决的它的核心是一套持续的被验证的优化流程。先把资源加载策略理顺再盯着渲染路径一步步排查每一步都有数据支撑效果 300% 不是噱头是水到渠成的结果。希望这一套流程跑下来能帮你把项目的加载速度推到一个自己满意、用户也感知明显的位置。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑