前端性能优化实战:如何把首屏加载时间减半
把首屏加载时间减半,性价比最高的动作只有四步:压缩传输体积、按路由拆包、让图片和字体不阻塞渲染、把关键资源提前到 HTML 解析之前。一套做下来,一个原本 LCP(Largest Contentful Paint,最大内容绘制,衡量首屏主要元素出现时间的核心指标)4.2 秒的中型项目,落到 1.9~2.1 秒是可复现的结果。
首屏加载时间到底该看哪个指标?
结论:别盯 window.onload,盯 LCP 和 TTFB,目标是 LCP ≤ 2.5 秒、TTFB ≤ 800 毫秒。
LCP 记录视口内最大文本块或图片的渲染完成时间,是 Google Core Web Vitals 的核心项;TTFB(Time To First Byte)反映服务端和网络链路,占首屏时间的三成左右。测量顺序是:先跑一次 Lighthouse(Chrome DevTools → Lighthouse → 勾选 Performance,移动端模拟 + 4G 节流),再用 WebPageTest 选一个国内节点跑三次取中位数,避免单次抖动误导。改完之后用同一条件复测,只看 LCP、FCP、TBT 三个数。
第一步怎么定位瓶颈:把首屏拆成四段
结论:首屏时间 = TTFB + 资源下载 + JS 解析执行 + 渲染,先算出哪一段占比最大,再决定优化顺序。
打开 DevTools → Performance 录制页面加载,看 Main 线程火焰图。典型分布是:TTFB 300 毫秒、下载 700 毫秒、JS 解析执行 1800 毫秒、渲染 400 毫秒——这种结构说明瓶颈在执行,不在网络,先做拆包而不是先加 CDN。反过来,如果下载段超过 1.5 秒,优先做压缩和 CDN。这一步不可跳过,否则容易把时间花在只占 10% 的环节上。
怎么把 JS 体积压下来?
结论:bundle 体积每减少 100KB(gzip 后),4G 网络下首屏约快 100~150 毫秒。
具体做法:
- 装
webpack-bundle-analyzer或rollup-plugin-visualizer生成体积图,按面积排序找最大的三块。 - 常见替换:
moment(约 290KB)换dayjs(约 7KB);lodash改为import debounce from 'lodash/debounce';UI 库开启按需引入。 - 开启 tree-shaking:
package.json里加"sideEffects": false,用 ES Module 语法而非 CommonJS。 - 开启 Brotli 压缩(
compression-webpack-plugin的brotli选项),同一份 JS,Brotli 比 gzip 再小 15%~20%。
代码分割怎么做才有效?
结论:按路由做动态 import,首屏只加载当前路由的 chunk,其余 chunk 在用户点击时再请求。
React 用 React.lazy(() => import('./Page')) 配 <Suspense fallback={...}>;Vue 用 () => import('./Page.vue');Vite 和 webpack 都会自动生成独立 chunk。再配 splitChunks 把 react、react-dom 这类长期不变的库拆到 vendor chunk,配合文件名 hash,用户二次访问直接命中缓存。注意:不要把首屏必需的组件也 lazy 掉,那会多一次网络往返,反而变慢。
图片和字体怎么不拖后腿?
结论:首屏图片用 WebP/AVIF 并显式写宽高,字体用 font-display: swap 加子集化,能砍掉 300~600 毫秒。
- 图片:同尺寸下 WebP 比 JPEG 小 25%~35%,AVIF 再小 20% 左右;给
<img>写width和height防止 CLS(布局偏移);首屏外的图加loading="lazy",LCP 那张图加fetchpriority="high"并禁止 lazy。 - 字体:
@font-face里加font-display: swap,中文用fonttools子集化后单文件可压到 100KB 以内,再用<link rel="preload" as="font" crossorigin>提前拉取。
关键请求怎么提前和复用?
结论:用 preconnect 提前建连、用长效缓存避免重复下载,是投入产出比最高的两项。
在 <head> 顶部加 <link rel="preconnect" href="https://cdn.example.com" crossorigin>,DNS + TCP + TLS 握手省下 100~300 毫秒;静态资源文件名带内容 hash,响应头设 Cache-Control: public, max-age=31536000, immutable,二次访问直接走本地缓存;有条件的上 HTTP/2 或 HTTP/3,多路复用能消掉队列阻塞。
优化前后怎么验证效果?
结论:每一项改动都要单独复测并记录数字,避免把回归当成优化。
建议做一张对比表,记录 LCP、FCP、TBT、首屏请求数、传输总量五项,改动前一组、改动后一组。判断标准:LCP 降幅超过 40% 才算有效;如果某次改动后 LCP 反而上升,先检查是不是多了 preload 冲突或 chunk 拆分过碎(单页 chunk 超过 15 个就要合并)。
总结一下:先量出四段时间占比,再按「压体积 → 按路由拆包 → 图片字体不阻塞 → 关键资源提前」的顺序改,每步都用同一条件复测。做到位,LCP 从 4 秒级降到 2 秒级不需要重构,只需要把上面这些默认开关打开。
原文链接:https://www.gj0.com/thread-90.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。