前端性能优化直接决定用户留存:首屏每慢 1 秒,转化率可能跌 7%。本文从 Core Web Vitals 出发,给出一套可落地的 Lighthouse 90+ 优化清单,覆盖加载、渲染、资源与代码四个层面,照着做就能把首屏压到 1 秒级。
一、先看懂三个核心指标
Google 的 Core Web Vitals(核心网页指标)是性能优化的北极星。Lighthouse 90+ 的本质,就是让这三个指标全部达标。很多团队一上来就乱加缓存,结果指标没动——因为没对准靶心。
| 指标 | 含义 | 优秀阈值 | 常见瓶颈 |
|---|---|---|---|
| LCP | 最大内容绘制(首屏主内容出现时间) | ≤ 2.5s | 大图、阻塞脚本、慢服务器 |
| INP | 下一次绘制交互延迟(取代旧 FID) | ≤ 200ms | 长任务、主线程阻塞 |
| CLS | 累积布局偏移(页面抖动) | ≤ 0.1 | 无尺寸的图片、异步插入广告 |
记住一个优先级:先把 LCP 做快,再压 INP,最后收 CLS。因为 LCP 对留存的拉动最大,而 CLS 往往是前两步顺带解决的副作用。这三个指标同时也是 Google 搜索排名的参考因素——性能好不仅留得住人,还能带来更自然的搜索流量,对技术站尤其划算。
二、加载阶段:让资源更快到达浏览器
2.1 压缩:Brotli 优先于 Gzip
文本资源(HTML/JS/CSS)压缩能省下 60%~80% 体积。现代浏览器普遍支持 Brotli,压缩率比 Gzip 高 15%~20%。如果你的站点跑在 Nginx 上,可参考我们之前写的Nginx Gzip 配置一文,把 Gzip 升级为 Brotli,或两者并存由浏览器协商。
2.2 缓存与 CDN:让重复访问接近 0 延迟
静态资源用「内容哈希文件名 + 长缓存」是性价比最高的手段:文件名带 hash(如 app.4f3a9.js),即可放心设一年缓存;HTML 则设 no-cache 保证及时更新。配合 CDN 边缘节点,能把首屏资源推到离用户最近的机房。反向代理层也别忘了加缓存头,详见Nginx 反向代理完整配置。
在此之上,用 preconnect / dns-prefetch 提前与关键域名建连,能再省下几百毫秒的握手时间;并确保服务器开启 HTTP/2(多路复用,告别队头阻塞)。这些动作几乎零成本,却常被忽略。
# 静态资源:一年强缓存(文件名已带 hash)
Cache-Control: public, max-age=31536000, immutable
# HTML:每次都校验
Cache-Control: no-cache
三、渲染阶段:缩短关键渲染路径
关键渲染路径(Critical Rendering Path)指浏览器把 HTML/CSS/JS 变成像素的过程。优化目标是「让首屏所需资源最少、最快执行」。诊断时打开 Chrome DevTools 的 Performance 面板录一段首屏,重点看「长任务(Long Task,红色区块)」——任何超过 50ms 的任务都可能拖累 INP,应尽量拆小或用 Web Worker 搬离主线程。
- 关键 CSS 内联:首屏样式直接写在
<head>,非关键 CSS 用preload异步加载。 - JS 不阻塞解析:第三方脚本用
defer或async,业务脚本用type="module"(天然 defer)。 - 避免布局抖动:读写 DOM 集中批量操作,别在循环里交替读
offsetHeight和写样式。
<!-- 阻塞渲染:应避免 -->
<script src="/analytics.js"></script>
<!-- 延迟执行:推荐 -->
<script defer src="/analytics.js"></script>
<script async src="/chat-widget.js"></script>
四、图片与字体:最容易被忽视的大头
多数页面的体积都被图片和字体吃掉。换格式 + 懒加载通常能带来最直观的 LCP 改善。
4.1 图片:WebP/AVIF 替代 PNG/JPG
AVIF 比 JPEG 小约 50%,WebP 小约 30%。用 <picture> 做渐进兼容,并给 loading="lazy" 让非首屏图延迟加载。首屏那张主图则要预加载(fetchpriority="high")。
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="首页主视觉"
fetchpriority="high" width="1200" height="630">
</picture>
<!-- 非首屏图:懒加载,根治 CLS -->
<img src="chart.png" loading="lazy" width="800" height="450" alt="数据图表">
4.2 字体:swap + 子集化
用 font-display: swap 避免「字体加载期间白屏」;中文字体务必子集化(只打包用到的字形),否则一个字体文件轻松上兆。需要时搭配 preload 关键字体即可。
五、代码分割:只加载当前页面需要的
单页应用最常见的性能坑是「首屏把整个站点的 JS 都下载了」。路由级代码分割能让每个页面只加载自己的代码。如果你在用 Vue3,可以参考我们整理的Vue3 + TypeScript 工程化实践,把路由改成动态导入。
// Vue Router:懒加载路由,按页切分 chunk
const routes = [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
]
// 第三方库按需引入(tree-shaking)
// 错误:import _ from 'lodash' // 整包被打入
import debounce from 'lodash/debounce' // 只引入需要的函数
用 rollup-plugin-visualizer 生成包体积图,一眼看出是哪个依赖在「偷重量」,再决定要不要换更轻的库或做按需加载。对于下一步可能访问的路由,还可以用 <link rel="prefetch"> 在浏览器空闲时预取,让用户点过去时几乎瞬时打开——这是「感知性能」比「真实性能」更讨喜的经典手法。
六、一份可照做的优化清单
- 资源:开 Brotli、静态资源长缓存 + CDN、HTML no-cache。
- 渲染:关键 CSS 内联、脚本 defer/module、避免布局抖动。
- 图片:AVIF/WebP、首屏图 high priority、其余 lazy。
- 字体:font-display:swap、中文子集化。
- 代码:路由级分割、第三方按需引入、定期看体积图。
- 度量:把 Lighthouse 跑进 CI,设性能预算,回归即报警。
七、验证:用 Lighthouse CI 闭环
优化不是一次性的,要写进流水线防止劣化。把 Lighthouse CI 接入构建,给 LCP/INP/CLS 设阈值,一旦某次提交让分数跌破 90 就阻断合并。很多团队性能做完就反弹,根因正是缺少这道自动护栏。
# lighthouserc.js(节选)
module.exports = {
ci: { assert: { assertions: {
'categories:performance': ['warn', { minScore: 0.9 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
} } }
}
八、总结
前端性能优化的杠杆点很集中:压缩与缓存解决「传得慢」,关键渲染路径解决「画得慢」,图片字体解决「占比大」,代码分割解决「载得多」。按本文清单逐项落地,Lighthouse 90+、首屏 1 秒级并不难;再把它固化进 CI,性能就不会随着需求迭代悄悄劣化。你下一步打算先动哪一项?欢迎在评论区聊聊。
九、三个最容易踩的误区
最后点名几个高频坑,避免你白费力气:
- 盲目上 PWA/Service Worker:首屏还没快起来就做离线缓存,等于给慢页面套了层缓存壳,用户第一次访问依旧卡。
- 只优化桌面、忽略移动端:移动端 CPU 与网络更弱,LCP 往往差 2~3 倍,必须以中端手机为基准测。
- 用本地 localhost 跑 Lighthouse 当满分:本地没有网络与服务器延迟,分数虚高;应在生产环境或限速到「慢 4G」的真实条件下评估。



