Frontend Performance Optimization Notes
Research shows that for every additional second of page load time, user churn can rise by 7%.
1. Core metrics: Web Vitals
Google’s Web Vitals are the core metrics for measuring user experience:
1.1 LCP (Largest Contentful Paint)
Largest Contentful Paint, measures how long it takes for the main content of a page to finish loading.
// 使用 Web Vitals 库监控 LCP
import { onLCP } from 'web-vitals';
onLCP(({ value, entries }) => {
console.log(`LCP: ${value}ms`);
// 优化目标: LCP < 2.5s
});
1.2 FID (First Input Delay)
First Input Delay, measures how quickly the page responds to the user’s first interaction.
import { onFID } from 'web-vitals';
onFID(({ value }) => {
console.log(`FID: ${value}ms`);
// 优化目标: FID < 100ms
});
1.3 CLS (Cumulative Layout Shift)
Cumulative Layout Shift, measures the visual stability of a page.
import { onCLS } from 'web-vitals';
onCLS(({ value }) => {
console.log(`CLS: ${value}`);
// 优化目标: CLS < 0.1
});
p s: You can test these metrics in one click using the Lighthouse panel in Chrome DevTools.
2. Resource optimization strategies
2.1 Resource compression and bundling
// webpack 配置示例
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
};
2.2 Image optimization
// 使用 Next.js Image 组件
import Image from 'next/image';
function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
placeholder="blur"
blurDataURL="data:image/..." // 模糊占位图
loading="lazy"
quality={75}
width={400}
height={300}
/>
);
}
2.3 WebP image format
<!-- 响应式图片使用 picture 标签 -->
<picture>
<source srcset="image.webp" type="image/webp" />
<source srcset="image.jpg" type="image/jpeg" />
<img src="image.jpg" alt="描述" />
</picture>
3. Caching strategies
3.1 Browser cache configuration
// Next.js next.config.js 配置
module.exports = {
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/:path*.json',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=3600, must-revalidate',
},
],
},
];
},
};
3.2 Service Worker caching
// sw.js
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// 缓存命中返回缓存,否则请求网络
return response || fetch(event.request);
})
);
});
4. Code splitting and lazy loading
4.1 Route-level code splitting
// React Router v6 + React.lazy
import { Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
4.2 Component-level lazy loading
import { useState, lazy, Suspense } from 'react';
function ModalContainer() {
const [showModal, setShowModal] = useState(false);
// 动态导入大型组件
const HeavyModal = lazy(() => import('./HeavyModal'));
return (
<>
<button onClick={() => setShowModal(true)}>打开弹窗</button>
{showModal && (
<Suspense fallback={<ModalLoading />}>
<HeavyModal onClose={() => setShowModal(false)} />
</Suspense>
)}
</>
);
}
4.3 Image lazy loading
// 使用 Intersection Observer 实现图片懒加载
function LazyImage({ src, alt }) {
const [isVisible, setIsVisible] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
});
});
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef}>
{isVisible && <img src={src} alt={alt} />}
</div>
);
}
5. Rendering optimization
5.1 Reducing reflow and repaint
// ❌ 避免:多次修改 DOM 触发多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';
// ✅ 推荐:使用 CSS 类一次性修改
element.classList.add('expanded');
// ✅ 推荐:使用 document fragment
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
ul.appendChild(fragment);
5.2 Using CSS Transform and Opacity
/* ❌ 影响布局的属性 */
.element {
width: 100px;
height: 100px;
top: 50px;
}
/* ✅ 不影响布局的属性 */
.element {
transform: translateX(50px);
opacity: 0.5;
}
5.3 React Performance Optimization
import { memo, useMemo, useCallback } from 'react';
// 使用 memo 避免不必要的重渲染
const ListItem = memo(({ item, onClick }) => {
return <li onClick={() => onClick(item.id)}>{item.name}</li>;
});
// 使用 useMemo 缓存计算结果
function ExpensiveComponent({ data, filter }) {
const filteredData = useMemo(() => {
return data.filter(item => item.name.includes(filter));
}, [data, filter]);
return filteredData.map(item => <ListItem key={item.id} item={item} />);
}
// 使用 useCallback 缓存回调函数
function Parent() {
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return <Child onClick={handleClick} />;
}
6. Network Optimization
6.1 CDN Configuration
// next.config.js 配置 CDN
module.exports = {
images: {
domains: ['cdn.example.com', 'images.unsplash.com'],
path: '/_next/image',
loader: 'default',
},
};
6.2 Preconnecting and Preloading
<!-- 预连接到关键域名 -->
<link rel="preconnect" href="https://cdn.example.com" />
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin />
<!-- 预取下一个路由 -->
<link rel="prefetch" href="/dashboard" />
6.3 Loading Third-Party Scripts Asynchronously
<!-- 方式 1: async -->
<script src="https://analytics.example.com/script.js" async></script>
<!-- 方式 2: defer -->
<script src="https://analytics.example.com/script.js" defer></script>
<!-- 方式 3: 动态加载 -->
<script>
const script = document.createElement('script');
script.src = 'https://analytics.example.com/script.js';
script.async = true;
document.head.appendChild(script);
</script>
7. Performance Testing Tools
7.1 Lighthouse
# 使用 Chrome CLI 运行 Lighthouse
lighthouse https://example.com \
--preset=desktop \
--view \
--output=json \
--output-path=./lighthouse-report.json
7.2 Web Vitals Field Metrics
// 完整的性能监控实现
import { getCLS, getFID, getLCP } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
// 发送到分析服务
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? value * 1000 : value),
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
8. Common Optimization Patterns
graph TD
A[用户请求] --> B{缓存检查}
B -->|命中| C[返回缓存资源]
B -->|未命中| D[请求服务器]
D --> E{资源类型}
E -->|静态资源| F[CDN 响应]
E -->|动态内容| G[服务器处理]
F --> H[存入浏览器缓存]
G --> I[生成响应]
H --> C
I --> C
9. Optimization Checklist
| Optimization Item | Target | Priority |
|---|---|---|
| LCP | < 2.5s | High |
| FID | < 100ms | High |
| CLS | < 0.1 | High |
| Image Compression | WebP + Responsive | High |
| Code Splitting | Split by Route | High |
| Caching Strategy | Static Assets for One Year | Medium |
| Lazy Loading | Non-Critical Resources | Medium |
| CDN | Global Nodes | Low |
10. Summary
Frontend performance optimization is a systematic effort that requires addressing multiple dimensions:
- Metric-driven: Use Web Vitals as the core metrics
- User experience: Focus on actual loading and interaction experience
- Progressive enhancement: Prioritize optimizing the critical rendering path
- Continuous monitoring: Establish a performance monitoring system
Note: Optimization should be targeted; over-optimization increases maintenance costs. Use Lighthouse for regular checks and focus on the issues that impact users the most.