Web 性能优化:Lighthouse不够用了之后
这次给公司官网做性能优化,从首屏加载 8 秒优化到 2 秒,也学到了不少。先用 Lighthouse 看看网站现状。
性能评估
Lighthouse 基础
先用 Lighthouse 看看网站现状。
# 在 Chrome DevTools 中运行 Lighthouse
# 或者使用命令行
npx lighthouse https://example.com --view
Lighthouse 给出的分数:
- Performance: 35
- Accessibility: 85
- Best Practices: 70
- SEO: 90
问题很明显:性能分数太低了。
关键指标
Lighthouse 的关键指标:
- FCP (First Contentful Paint): 首次内容绘制 2.5 秒
- LCP (Largest Contentful Paint): 最大内容绘制 8 秒
- TTI (Time to Interactive): 可交互时间 9 秒
- CLS (Cumulative Layout Shift): 累积布局偏移 0.1
- FID (First Input Delay): 首次输入延迟 100 毫秒
LCP 8 秒太慢了,用户可能早就不耐烦了。
优化策略
资源压缩
静态资源太大,压缩效果明显。
图片优化:
# 使用 WebP 格式
ffmpeg -i image.png -c:v libwebp -quality 80 image.webp
# 压缩 PNG
optipng -o7 image.png
# 压缩 JPEG
jpegoptim --max=80 image.jpg
代码压缩:
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
};
结果:静态资源大小减少了 60%。
懒加载
非首屏资源不要急着加载。
图片懒加载:
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="Description">
代码分割:
// 动态导入
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// 在路由中使用
<Route path="/dashboard" component={LazyComponent} />
结果:首屏加载的代码减少了 40%。
缓存策略
浏览器缓存能减少重复请求。
HTTP 缓存头:
# 静态资源长期缓存
location ~* \.(jpg|jpeg|png|gif|webp|svg|woff|woff2)$ {
expires: 1y;
add_header Cache-Control "public, immutable";
}
# HTML 文件短期缓存
location ~* \.html$ {
expires: 1h;
add_header Cache-Control "public";
}
# API 响应短期缓存
location /api/ {
expires: 5m;
add_header Cache-Control "public, must-revalidate";
}
Service Worker 缓存:
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
'/',
'/static/css/main.css',
'/static/js/main.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => response || fetch(event.request))
);
});
结果:重复访问速度提升了 80%。
关键渲染路径优化
阻塞渲染的资源
CSS 阻塞渲染,JavaScript 阻塞解析。
优化策略:
<!-- 关键 CSS 内联 -->
<style>
body { font-family: sans-serif; }
.header { ... }
</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- JavaScript 异步加载 -->
<script src="non-critical.js" async></script>
<script src="defer.js" defer></script>
结果:FCP 从 2.5 秒降低到 1.2 秒。
预加载重要资源
预加载可以提前开始加载关键资源。
<!-- 预连接到重要域名 -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image">
结果:LCP 从 8 秒降低到 4 秒。
API 优化
后端接口太慢,前端再优化也没用。
请求合并
多个小请求合并成一个大请求。
// 之前的做法
fetch('/api/user/profile').then(r => r.json());
fetch('/api/user/settings').then(r => r.json());
fetch('/api/user/notifications').then(r => r.json());
// 优化后
fetch('/api/user/all').then(r => r.json());
响应压缩
# 开启 Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
# 开启 Brotli 压缩(需要安装 ngx_brotli)
brotli on;
brotli_types text/plain text/css application/json application/javascript;
结果:API 响应大小减少了 70%,传输时间减少了 60%。
踩过的坑
坑一:过度优化
一开始想什么都优化,结果花了很多时间在不重要的地方。
解决:先看 Lighthouse 报告,优先优化影响最大的问题。
坑二:缓存失效
改了静态资源,但用户还在用旧缓存。
解决:给静态资源加上版本号。
// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
},
};
坑三:第三方脚本拖慢速度
引入了太多第三方脚本(Analytics、聊天工具、广告)。
解决:
- 延迟加载非关键脚本
- 用 Service Worker 缓存第三方脚本
- 定期审查是否还需要这些脚本
<!-- 第三方脚本延迟加载 -->
<script>
window.addEventListener('load', () => {
const script = document.createElement('script');
script.src = 'https://analytics.example.com/script.js';
document.body.appendChild(script);
});
</script>
监控与持续优化
性能优化不是一次性工作,需要持续监控。
RUM (Real User Monitoring)
收集真实用户的性能数据。
// 使用 Web Vitals 库
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
定期检查
每个月运行一次 Lighthouse,确保性能不会退化。
# 定期运行 Lighthouse
0 0 1 * * npx lighthouse https://example.com --output=json --output-path=report.json
写在最后
Web 性能优化这东西,不是追求 100 分,而是追求合理的投入产出比。
优化优先级:
- LCP(最大内容绘制)- 影响最大
- FCP(首次内容绘制)- 其次重要
- TTI(可交互时间)- 影响用户体验
- CLS(累积布局偏移)- 影响视觉稳定性
- FID(首次输入延迟)- 影响交互响应
优化之前先评估:
- 用户的使用场景
- 网络环境
- 设备性能
- 业务价值
不是所有优化都值得做,先做影响最大的。
这次性能优化花了三周时间,LCP 从 8 秒优化到 2 秒,跳出率降低了 30%,转化率提升了 15%。性能优化的价值确实明显。
版权声明: 本文首发于 指尖魔法屋-Web 性能优化:Lighthouse不够用了之后(https://blog.thinkmoon.cn/post/58-web-performance-optimization-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。