Lighthouse 性能审计
问题
什么是 Lighthouse?如何使用 Lighthouse 进行性能审计?如何在 CI/CD 中集成 Lighthouse?
答案
Lighthouse 是 Google 开源的网页质量审计工具,用于测量和改进网页的性能、可访问性、最佳实践、SEO 等维度。它是前端性能优化的重要工具。
Lighthouse 简介
审计维度
评分计算
| 分数范围 | 评级 | 颜色 |
|---|---|---|
| 90-100 | 优秀 | 🟢 绿色 |
| 50-89 | 需改进 | 🟠 橙色 |
| 0-49 | 差 | 🔴 红色 |
Performance 评分权重
| 指标 | 权重 | 说明 |
|---|---|---|
| TBT (Total Blocking Time) | 30% | 阻塞时间 |
| LCP (Largest Contentful Paint) | 25% | 最大内容绘制 |
| CLS (Cumulative Layout Shift) | 25% | 累积布局偏移 |
| FCP (First Contentful Paint) | 10% | 首次内容绘制 |
| SI (Speed Index) | 10% | 速度指数 |
使用方式
1. Chrome DevTools
1. 打开 Chrome DevTools (F12)
2. 切换到 Lighthouse 面板
3. 选择审计类别和设备类型
4. 点击 "Analyze page load"
2. Chrome 扩展
1. 安装 Lighthouse Chrome 扩展
2. 点击扩展图标
3. 选择审计选项
4. 生成报告
3. 命令行工具
- npm
- Yarn
- pnpm
- Bun
npm install -g lighthouse
yarn global add lighthouse
pnpm add -g lighthouse
bun add --global lighthouse
# 基本使用
lighthouse https://example.com
# 指定输出格式
lighthouse https://example.com --output html --output-path ./report.html
# 指定设备
lighthouse https://example.com --preset=desktop
# 只审计性能
lighthouse https://example.com --only-categories=performance
# JSON 输出
lighthouse https://example.com --output json --output-path ./report.json
4. Node.js API
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';
async function runLighthouse(url: string) {
// 启动 Chrome
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
// 运行 Lighthouse
const options = {
logLevel: 'info' as const,
output: 'json' as const,
onlyCategories: ['performance'],
port: chrome.port,
};
const result = await lighthouse(url, options);
// 关闭 Chrome
await chrome.kill();
return result;
}
// 使用
const result = await runLighthouse('https://example.com');
console.log('Performance Score:', result?.lhr.categories.performance.score);
核心性能指标
Performance 指标详解
| 指标 | 全称 | 良好 | 需改进 | 差 |
|---|---|---|---|---|
| FCP | First Contentful Paint | <1.8s | 1.8-3s | >3s |
| LCP | Largest Contentful Paint | <2.5s | 2.5-4s | >4s |
| TBT | Total Blocking Time | <200ms | 200-600ms | >600ms |
| CLS | Cumulative Layout Shift | <0.1 | 0.1-0.25 | >0.25 |
| SI | Speed Index | <3.4s | 3.4-5.8s | >5.8s |
指标含义
// FCP - 首次内容绘制
// 浏览器首次渲染任何文本、图片、非空白 canvas 或 SVG 的时间
// LCP - 最大内容绘制
// 视口内最大的图片、视频或文本块完成渲染的时间
// 常见 LCP 元素:<img>、<video>、背景图、大块文本
// TBT - 总阻塞时间
// FCP 到 TTI 之间,主线程被长任务阻塞的总时间
// 长任务 = 执行时间 > 50ms 的任务
// CLS - 累积布局偏移
// 衡量视觉稳定性,页面加载期间意外布局移动的总和
// SI - 速度指数
// 页面内容可见的填充速度,越低越好
常见优化建议
1. 减少渲染阻塞资源
<!-- ❌ 阻塞渲染 -->
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
<!-- ✅ 优化后 -->
<!-- 关键 CSS 内联 -->
<style>/* 关键样式 */</style>
<!-- 非关键 CSS 延迟加载 -->
<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'">
<!-- JS 异步加载 -->
<script src="app.js" defer></script>
2. 减少未使用的 JavaScript
// 使用代码分割
const LazyComponent = lazy(() => import('./LazyComponent'));
// 使用 Tree Shaking
// 只导入需要的函数
import { debounce } from 'lodash-es';
// 而不是
import _ from 'lodash';
3. 服务静态资源使用高效缓存策略
# nginx 配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
4. 延迟加载屏幕外图片
<img src="image.jpg" loading="lazy" alt="描述">
5. 避免巨大的网络负载
// 使用现代图片格式
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述">
</picture>
// 压缩资源
// Brotli 压缩(比 gzip 小 15-20%)
CI/CD 集成
GitHub Actions
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Start server
run: npm run preview &
- name: Wait for server
run: sleep 10
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v11
with:
urls: |
http://localhost:3000
http://localhost:3000/about
uploadArtifacts: true
temporaryPublicStorage: true
configPath: './lighthouserc.json'
Lighthouse CI 配置
// lighthouserc.json
{
"ci": {
"collect": {
"url": ["http://localhost:3000"],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["warn", { "minScore": 0.9 }],
"categories:best-practices": ["warn", { "minScore": 0.9 }],
"categories:seo": ["warn", { "minScore": 0.9 }],
"first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 300 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
自动化脚本
// lighthouse-ci.ts
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';
interface AuditResult {
url: string;
scores: Record<string, number>;
passed: boolean;
}
async function auditUrl(url: string): Promise<AuditResult> {
const chrome = await chromeLauncher.launch({
chromeFlags: ['--headless', '--no-sandbox']
});
const result = await lighthouse(url, {
port: chrome.port,
output: 'json',
});
await chrome.kill();
const lhr = result?.lhr;
if (!lhr) throw new Error('Lighthouse failed');
const scores: Record<string, number> = {};
Object.entries(lhr.categories).forEach(([key, category]) => {
scores[key] = Math.round((category.score || 0) * 100);
});
// 检查是否通过阈值
const passed = scores.performance >= 90 &&
scores.accessibility >= 90 &&
scores['best-practices'] >= 90;
return { url, scores, passed };
}
async function runAudit() {
const urls = [
'http://localhost:3000',
'http://localhost:3000/about',
];
const results = await Promise.all(urls.map(auditUrl));
console.table(results.map(r => ({
URL: r.url,
Performance: r.scores.performance,
Accessibility: r.scores.accessibility,
'Best Practices': r.scores['best-practices'],
SEO: r.scores.seo,
Passed: r.passed ? '✅' : '❌',
})));
const allPassed = results.every(r => r.passed);
process.exit(allPassed ? 0 : 1);
}
runAudit();
性能监控集成
PageSpeed Insights API
async function getPageSpeedInsights(url: string) {
const apiKey = process.env.PSI_API_KEY;
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed`;
const params = new URLSearchParams({
url,
key: apiKey || '',
strategy: 'mobile',
category: 'PERFORMANCE',
category: 'ACCESSIBILITY',
});
const response = await fetch(`${endpoint}?${params}`);
const data = await response.json();
return {
performance: data.lighthouseResult.categories.performance.score * 100,
fcp: data.lighthouseResult.audits['first-contentful-paint'].displayValue,
lcp: data.lighthouseResult.audits['largest-contentful-paint'].displayValue,
cls: data.lighthouseResult.audits['cumulative-layout-shift'].displayValue,
};
}
定时监控
// 使用 cron 定时运行
import cron from 'node-cron';
cron.schedule('0 */6 * * *', async () => {
// 每 6 小时运行一次
const result = await auditUrl('https://mysite.com');
if (result.scores.performance < 80) {
// 发送告警
await sendAlert({
type: 'performance_degradation',
score: result.scores.performance,
});
}
// 保存历史数据
await saveToDatabase(result);
});
报告分析
常见问题诊断
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| LCP 高 | 图片未优化 | WebP/懒加载/preload |
| FCP 高 | 渲染阻塞资源 | async/defer/内联关键CSS |
| TBT 高 | 长任务 | 代码分割/Web Worker |
| CLS 高 | 图片无尺寸 | 设置 width/height |
| 低分 | 第三方脚本 | 延迟加载/async |
追踪优化效果
// 记录优化前后的分数变化
interface PerformanceRecord {
date: Date;
scores: {
performance: number;
accessibility: number;
bestPractices: number;
seo: number;
};
metrics: {
fcp: number;
lcp: number;
tbt: number;
cls: number;
};
}
// 生成趋势图
function generateTrendReport(records: PerformanceRecord[]) {
const trend = records.map(r => ({
date: r.date.toISOString().split('T')[0],
performance: r.scores.performance,
lcp: r.metrics.lcp,
}));
console.table(trend);
}
常见面试问题
Q1: Lighthouse 评估哪些维度?
答案:
| 维度 | 说明 | 核心指标 |
|---|---|---|
| Performance | 页面性能 | FCP、LCP、TBT、CLS、SI |
| Accessibility | 可访问性 | 颜色对比度、ARIA 标签 |
| Best Practices | 最佳实践 | HTTPS、无控制台错误 |
| SEO | 搜索优化 | meta 标签、语义化 |
| PWA | 渐进式 Web 应用 | manifest、离线支持 |
Q2: 如何提高 Lighthouse Performance 分数?
答案:
Q3: Lighthouse 分数不稳定怎么办?
答案:
- 多次运行取平均值
lighthouse --num-runs=5 --median-run
- 使用稳定的测试环境
// 使用 Docker 隔离环境
// 关闭浏览器扩展
// 使用有线网络
- 排除网络因素
// 使用 throttling 模拟一致的网络条件
const options = {
throttling: {
cpuSlowdownMultiplier: 4,
downloadThroughputKbps: 1638,
uploadThroughputKbps: 675,
rttMs: 150,
},
};
Q4: 如何在 CI/CD 中使用 Lighthouse?
答案:
# GitHub Actions 示例
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v11
with:
urls: http://localhost:3000
configPath: ./lighthouserc.json
# 阈值检查
assertions:
categories:performance: ["error", { "minScore": 0.9 }]
largest-contentful-paint: ["error", { "maxNumericValue": 2500 }]
Q5: Lighthouse 和真实用户数据的区别?
答案:
| 特性 | Lighthouse (Lab Data) | RUM (Field Data) |
|---|---|---|
| 数据来源 | 模拟环境 | 真实用户 |
| 一致性 | 高 | 取决于用户环境 |
| 实时性 | 即时 | 需要收集 |
| 网络条件 | 固定模拟 | 实际网络 |
| 用途 | 开发调试 | 监控真实性能 |
最佳实践:两者结合使用
- Lighthouse:开发阶段快速反馈
- RUM (Core Web Vitals):监控真实用户体验