技术
15 min read前端性能优化实战:从 Lighthouse 到 Core Web Vitals
2025-01-16by LZG
#Performance#Frontend#Optimization
引言
前端性能优化是提升用户体验的关键。本文将从 Core Web Vitals 指标出发,结合实际项目经验,分享系统性的性能优化策略。
1. Core Web Vitals 核心指标
1.1 LCP (Largest Contentful Paint)
定义:页面主要内容加载完成的时间
目标:< 2.5s
优化策略:
<!-- 1. 优化关键资源加载 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">
<!-- 2. 图片优化 -->
<img
src="hero.webp"
loading="eager"
width="1200"
height="600"
fetchpriority="high"
alt="Hero image"
>
<!-- 3. 避免阻塞渲染 -->
<script defer src="/analytics.js"></script>
1.2 FID (First Input Delay)
定义:用户首次交互到页面响应的时间
目标:< 100ms
优化策略:
// 1. 代码分割
import { lazy } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
// 2. 避免长任务
function processLargeArray(array) {
const result = [];
const chunkSize = 1000;
for (let i = 0; i < array.length; i += chunkSize) {
const chunk = array.slice(i, i + chunkSize);
result.push(...processChunk(chunk));
// 让出主线程
if (i % 5000 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
return result;
}
// 3. 使用 Web Worker
const worker = new Worker('data-processor.js');
worker.postMessage(largeData);
1.3 CLS (Cumulative Layout Shift)
定义:页面内容在加载过程中的视觉稳定性
目标:< 0.1
优化策略:
/* 1. 为图片预留空间 */
.image-container {
aspect-ratio: 16 / 9;
background-color: #f0f0f0;
}
/* 2. 使用 font-display */
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom.woff2');
font-display: swap;
}
/* 3. 避免动态插入内容 */
.ad-banner {
min-height: 250px;
}
2. 资源优化
2.1 图片优化
// 使用 Next.js Image 组件
import Image from 'next/image';
function OptimizedImage() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
/>
);
}
// 响应式图片
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
2.2 代码分割
// 路由级别分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
// 组件级别分割
const Chart = lazy(() => import('./components/Chart'));
const Table = lazy(() => import('./components/Table'));
// 预加载关键路由
const prefetchDashboard = () => import('./pages/Dashboard');
2.3 Tree Shaking
// ✅ 使用命名导入
import { debounce } from 'lodash-es';
// ❌ 避免默认导入
import _ from 'lodash';
// 配置 package.json
{
"sideEffects": false,
"exports": {
".": {
"import": "./index.js",
"require": "./index.cjs"
}
}
}
3. 缓存策略
3.1 HTTP 缓存
// Next.js 缓存配置
export async function getData() {
const res = await fetch('https://api.example.com/data', {
next: {
revalidate: 3600, // 1 小时
tags: ['products']
}
});
return res.json();
}
// 按需重新验证
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('products');
return Response.json({ revalidated: true });
}
3.2 Service Worker 缓存
// 缓存策略
const CACHE_STRATEGIES = {
cacheFirst: async (request) => {
const cached = await caches.match(request);
return cached || fetch(request);
},
networkFirst: async (request) => {
try {
const network = await fetch(request);
const cache = await caches.open('v1');
cache.put(request, network.clone());
return network;
} catch {
return caches.match(request);
}
},
staleWhileRevalidate: async (request) => {
const cache = await caches.open('v1');
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
};
4. 渲染优化
4.1 虚拟滚动
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`
}}
>
{items[virtualItem.index]}
</div>
))}
</div>
</div>
);
}
4.2 React 优化
// 1. 使用 memo
const ExpensiveComponent = memo(({ data }) => {
return <div>{/* 渲染逻辑 */}</div>;
});
// 2. 使用 useMemo
function Component({ items }) {
const sorted = useMemo(() => {
return [...items].sort((a, b) => a.value - b.value);
}, [items]);
return <List items={sorted} />;
}
// 3. 使用 useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <Child onClick={handleClick} />;
}
// 4. 避免不必要的渲染
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
<Item data={item} />
</li>
))}
</ul>
);
}
5. 监控与分析
5.1 性能监控
// Web Vitals 监控
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
const url = 'https://analytics.example.com/vitals';
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body);
} else {
fetch(url, { body, method: 'POST', keepalive: true });
}
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
5.2 自定义性能指标
// 测量特定操作
function measurePerformance(name, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
const duration = end - start;
console.log(`${name} took ${duration.toFixed(2)}ms`);
return result;
}
// 使用 Performance API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
});
observer.observe({ entryTypes: ['measure', 'navigation'] });
6. 实战案例
6.1 电商首页优化
问题:首屏加载时间 4.5s,LCP 评分 45
优化措施:
- 图片懒加载和 WebP 格式
- 关键 CSS 内联
- 第三方脚本延迟加载
- 服务端渲染关键内容
结果:首屏加载时间降至 1.8s,LCP 评分提升至 92
6.2 管理后台优化
问题:列表页面渲染缓慢,FID 超标
优化措施:
- 虚拟滚动实现
- 分页加载
- 防抖搜索
- Web Worker 处理大数据
结果:列表渲染时间从 2s 降至 200ms,FID 评分提升至 95
结语
前端性能优化是一个持续的过程,需要结合具体场景选择合适的策略。通过监控 Core Web Vitals 指标,系统性地优化资源加载、渲染性能和用户体验,我们可以构建出高性能的 Web 应用。记住:性能优化不是一次性的工作,而是需要持续关注和改进的过程。