
前言
用户体验的核心是速度。我们的产品页面加载时间曾经高达3秒,用户流失率居高不下。经过两个月的优化,我们将加载时间降到了300ms,用户留存率提升了40%。
这篇文章分享我们的优化过程和实战经验。
一、问题诊断:找到性能瓶颈
首先,我们用Chrome DevTools的Performance面板分析了页面加载过程:
页面加载时间分解:
- HTML加载:200ms
- CSS加载:500ms
- JavaScript加载:1200ms
- 图片加载:800ms
- API请求:300ms
总计:3000ms
问题很明显:JavaScript和CSS体积过大。
二、优化策略一:代码分割
2.1 问题:单个JS文件过大
我们的bundle.js大小是2.5MB,包含了所有页面的代码。
javascript
Copy code
// 原始代码:所有路由都在一个文件里 import Home from './pages/Home'; import Products from './pages/Products'; import Cart from './pages/Cart'; import Checkout from './pages/Checkout'; import Profile from './pages/Profile'; const routes = [ { path: '/', component: Home }, { path: '/products', component: Products }, { path: '/cart', component: Cart }, { path: '/checkout', component: Checkout }, { path: '/profile', component: Profile } ];
2.2 解决方案:动态导入
javascript
// 优化后:按需加载 const routes = [ { path: '/', component: () => import('./pages/Home') }, { path: '/products', component: () => import('./pages/Products') }, { path: '/cart', component: () => import('./pages/Cart') }, { path: '/checkout', component: () => import('./pages/Checkout') }, { path: '/profile', component: () => import('./pages/Profile') } ];
结果:
- 首页JS从2.5MB降到350KB
- 首次加载时间从1200ms降到200ms
三、优化策略二:图片优化
3.1 问题:图片未压缩
我们的产品图片平均大小是800KB,一个页面有10张图片,总共8MB。
3.2 解决方案:WebP格式 + 懒加载
javascript
// 图片懒加载组件 import React, { useState, useEffect, useRef } from 'react'; function LazyImage({ src, alt }) { const [isVisible, setIsVisible] = useState(false); const imgRef = useRef(); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true); observer.disconnect(); } }, { threshold: 0.1 } ); if (imgRef.current) { observer.observe(imgRef.current); } return () => observer.disconnect(); }, []); return ( <div ref={imgRef}> {isVisible ? ( <picture> <source srcSet={`${src}.webp`} type="image/webp" /> <img src={src} alt={alt} loading="lazy" /> </picture> ) : ( <div className="placeholder" style={````{ height: '300px' }} /> )} </div> ); } export default LazyImage;
结果:
- 图片大小从800KB降到80KB(WebP格式)
- 首屏只加载可见图片,减少90%的图片请求
四、优化策略三:CSS优化
4.1 问题:未使用的CSS
我们的styles.css有500KB,但很多样式从未被使用。
4.2 解决方案:PurgeCSS
javascript
// postcss.config.js module.exports = { plugins: [ require('tailwindcss'), require('autoprefixer'), process.env.NODE_ENV === 'production' && require('@fullhuman/postcss-purgecss')({ content: [ './src/**/*.html', './src/**/*.jsx', './src/**/*.js' ], defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [] }) ] };
结果:
- CSS从500KB降到50KB
- CSS加载时间从500ms降到50ms
五、优化策略四:API请求优化
5.1 问题:串行请求
javascript
// 原始代码:串行请求 async function loadPageData() { const user = await fetch('/api/user').then(r => r.json()); const products = await fetch('/api/products').then(r => r.json()); const cart = await fetch('/api/cart').then(r => r.json()); return { user, products, cart }; }
5.2 解决方案:并行请求
javascript
// 优化后:并行请求 async function loadPageData() { const [user, products, cart] = await Promise.all([ fetch('/api/user').then(r => r.json()), fetch('/api/products').then(r => r.json()), fetch('/api/cart').then(r => r.json()) ]); return { user, products, cart }; }
结果:
- API请求时间从900ms降到300ms
六、优化策略五:缓存策略
6.1 浏览器缓存
javascript
// webpack.config.js module.exports = { output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js' }, optimization: { runtimeChunk: 'single', splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } } };
6.2 Service Worker缓存
javascript
// service-worker.js const CACHE_NAME = 'app-v1'; const urlsToCache = [ '/', '/static/css/main.css', '/static/js/main.js' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });
结果:
- 二次访问加载时间从3000ms降到100ms
七、优化策略六:预加载和预连接
html
<!DOCTYPE html> <html> <head> <!-- DNS预解析 --> <link rel="dns-prefetch" href="https://api.example.com"> <!-- 预连接 --> <link rel="preconnect" href="https://api.example.com"> <!-- 预加载关键资源 --> <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/css/critical.css" as="style"> <!-- 预获取下一页资源 --> <link rel="prefetch" href="/pages/products.js"> </head> <body> <!-- 页面内容 --> </body> </html>
八、国际化团队的协作
在优化过程中,我们的前端团队分布在多个国家。为了确保技术文档和性能报告能够被所有团队成员理解,我们使用了同言翻译(Transync AI)来翻译技术文档,提高了团队协作效率。
九、性能监控
javascript
// 性能监控 if ('PerformanceObserver' in window) { // 监控First Contentful Paint const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { console.log('FCP:', entry.startTime); // 上报到监控系统 reportMetric('FCP', entry.startTime); } } }); observer.observe({ entryTypes: ['paint'] }); } // 监控Largest Contentful Paint const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; console.log('LCP:', lastEntry.startTime); reportMetric('LCP', lastEntry.startTime); }); observer.observe({ entryTypes: ['largest-contentful-paint'] }); // 监控Cumulative Layout Shift let clsScore = 0; const clsObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsScore += entry.value; } } console.log('CLS:', clsScore); reportMetric('CLS', clsScore); }); clsObserver.observe({ entryTypes: ['layout-shift'] });
十、性能对比
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首次加载时间 | 3000ms | 300ms | -90% |
| JS体积 | 2.5MB | 350KB | -86% |
| CSS体积 | 500KB | 50KB | -90% |
| 图片加载 | 8MB | 800KB | -90% |
| Lighthouse分数 | 45 | 95 | +111% |
| 用户留存率 | 60% | 84% | +40% |
十一、最佳实践总结
- 代码分割:按路由和组件拆分代码;
- 图片优化:使用WebP格式,实施懒加载;
- CSS优化:移除未使用的样式;
- 并行请求:同时发起多个API请求;
- 缓存策略:利用浏览器缓存和Service Worker;
- 预加载:预加载关键资源;
- 性能监控:持续监控核心指标。
十二、工具推荐
- Lighthouse:综合性能评分
- WebPageTest:详细的加载瀑布图
- Chrome DevTools:实时性能分析
- Bundle Analyzer:分析JS包大小
- ImageOptim:图片压缩工具
十三、结语
前端性能优化是一个持续的过程。每一个优化都会带来用户体验的提升,最终转化为业务指标的增长。
希望这篇文章能帮助你优化自己的项目。如果你有其他优化经验,欢迎在评论区分享!
